компиляторы и обработка шаблонов
К примеру есть такой шаблон:
template <typename ValueType, template <typename> typename Container>
void foo(const Container<ValueType> &container) {
std::cout << "hello" << std::endl;
}
в котором один из шаблонных параметром и сам является шаблоном. Насколько мне известно, это вполне валидная конструкция. А теперь сделаем небольшой пример:
// main.cpp
#include <cstdlib>
#include <vector>
template <typename ValueType, template <typename> typename Container>
void foo(const Container<ValueType> &container) {
}
int main() {
std::vector<int> tmp;
foo(tmp);
return EXIT_SUCCESS;
}
Данный пример без проблем компилируется с помощью gcc-8.3.0, однако не компилируется с помощью clang-9.0.1, который выдает следующую ошибку:
candidate template ignored: substitution failure [with ValueType = int]:
template template argument has different template parameters than its
corresponding template template parameter
void foo(const Container<ValueType> &container) {
^
Почему так происходит? Это баг?
Ответы (3 шт):
Вектор имеет не один шаблонный параметр, а два: https://en.cppreference.com/w/cpp/container/vector
В целом поведение CLang соответствует стандарту языка, так как возникает несоответствие количества шаблонных параметров. Отсюда и ошибка.
Вот так будет работать:
#include <vector>
template <
typename ValueType,
template <typename, typename> class Container
> void foo(
const Container<ValueType, std::allocator<ValueType>>& container
)
{}
int main() {
std::vector<int> tmp;
foo(tmp);
return 0;
}
Стоит заметить, что контейнеры STL имеют в общем случае разное количество параметров. В этом случае имеет смысл следующий код:
#include <vector>
#include <list>
#include <set>
template <
typename... Params,
template <typename...> class Container
> void foo(
const Container<Params...>& container
)
{}
int main() {
std::vector<int> vec; // 2 шаблонных параметра
std::list<double> lst; // 2 шаблонных параметра
std::set<int> set; // 3 шаблонных параметра
foo(vec);
foo(lst);
foo(set);
return 0;
}
UPD: В комментариях написал дополнительную информацию о процессе компиляции кода из вопроса - переношу сюда.
std::vector<int> для компилятора выглядит не иначе, как std::vector<int, std::allocator<int>>, - т.е. как тип с ДВУМЯ шаблонными параметрами. Компилятор, пытаясь сматчить этот тип с указанным в сигнатуре функции шаблоном, натыкается на несоответствие количества параметров и очень сильно по этому поводу расстраивается. Таким образом, необходимо обеспечивать соответствие количества параметров шаблона и соответствующего шаблонного шаблонного параметра. Причем стоит помнить, что дефолтные аргументы не перестают быть аргументами - для компилятора они ничуть не хуже явных!
Что касается поведения GCC, то это отклонение от стандарта (ну либо баг). На GodBolt и в GCC 8.3 не компилится.
В точке инстанцирования шаблона класса определяется конкретный тип, поэтому конструкция приведенная вами тут абсолютно излишняя. Тип выводится по аргументу шаблона функции:
template <typename Container>
void foo(const Container& container) {}
int main() {
std::vector<int> tmp;
foo( tmp);
return 0;
}
Приведенная вами конструкция используется абсолютно для других целей _ когда терминами параметров шаблона хотим выразить шаблонный тип. Например:
template <typename ValueType, template <typename> typename Container>
struct A {
ValueType val;
Container<int> cont; // любой контейнер инстанцированный int
};
Тогда обьект
A<double, std::vector> a;
будет хранить double и std::vector<int>
Если использовать шаблонный параметр шаблона, то тогда уж так:
template <typename ValueType, template <typename A, typename B = std::allocator<A>> typename Container>
void foo(const Container<ValueType> &container) {
}
int main() {
std::vector<int> tmp;
foo(tmp);
return EXIT_SUCCESS;
}
Ведь вектор - он имеет два параметра шаблона...
P.S. Рекомендую сию книгу, стр. 118.