компиляторы и обработка шаблонов

К примеру есть такой шаблон:

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 шт):

Автор решения: Georgy Firsov

Вектор имеет не один шаблонный параметр, а два: 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;
}

https://godbolt.org/z/x5MuJs

Стоит заметить, что контейнеры 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;
}

https://godbolt.org/z/JtVp8f

UPD: В комментариях написал дополнительную информацию о процессе компиляции кода из вопроса - переношу сюда.

std::vector<int> для компилятора выглядит не иначе, как std::vector<int, std::allocator<int>>, - т.е. как тип с ДВУМЯ шаблонными параметрами. Компилятор, пытаясь сматчить этот тип с указанным в сигнатуре функции шаблоном, натыкается на несоответствие количества параметров и очень сильно по этому поводу расстраивается. Таким образом, необходимо обеспечивать соответствие количества параметров шаблона и соответствующего шаблонного шаблонного параметра. Причем стоит помнить, что дефолтные аргументы не перестают быть аргументами - для компилятора они ничуть не хуже явных!

Что касается поведения GCC, то это отклонение от стандарта (ну либо баг). На GodBolt и в GCC 8.3 не компилится.

→ Ссылка
Автор решения: AR Hovsepyan

В точке инстанцирования шаблона класса определяется конкретный тип, поэтому конструкция приведенная вами тут абсолютно излишняя. Тип выводится по аргументу шаблона функции:

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>

→ Ссылка
Автор решения: Harry

Если использовать шаблонный параметр шаблона, то тогда уж так:

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.

→ Ссылка