Подтвердить что ты не робот

Почему пакет параметров шаблонов не будет выведен для нескольких аргументов типа при вызове функции?

У меня есть шаблон templated для параметра типа и пакета параметров, и я путаюсь о типе-выводе этого типа; при написании оператора потоковой передачи я обнаружил, что пакет параметров на operator<< не будет соответствовать параметрам типа и пакета для класса шаблона:

#include <iostream>

template<class T, class... Ts>
struct foo
{ /* ... */ };

template< class... Ts >
std::ostream& operator<<( std::ostream& os, const foo<Ts...>& )
{
  return os << 42;
}


int main()
{
  std::cout << foo<int>();
}

Это не скомпилируется как для gcc-4.7.2, так и для clang-3.0, поэтому, я думаю, я не понимаю правила здесь.

gcc говорит (где строка 16 является вызовом выходного потока):

t.cpp:16:28: error: cannot bind ‘std::ostream {aka std::basic_ostream<char>}’ lvalue to ‘std::basic_ostream<char>&&’
In file included from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/iostream:40:0,
                 from t.cpp:1:
/usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/ostream:600:5: error:   initializing argument 1 of ‘std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = foo<int>]’

и clang говорит:

t.cpp:16:16: error: invalid operands to binary expression ('ostream' (aka 'basic_ostream<char>') and 'foo<int>')
        std::cout << foo<int>();
        ~~~~~~~~~ ^  ~~~~~~~~~~

[--- snip: lots of non-viable candidates from standard library ---]

t.cpp:8:19: note: candidate template ignored: substitution failure [with Ts = <>]
    std::ostream& operator<<( std::ostream& os, const foo<Ts...>& )
                  ^

Может кто-нибудь рассказать мне, почему пакет параметров для operator<< не может быть выведен как параметр типа и пакет параметров для foo?

4b9b3361

Ответ 1

Что происходит, так это то, что функция шаблона с пакетом параметров шаблона class... Ts и тип параметра (P) из foo<Ts...> выводится в отношении типа аргумента (A) foo<int>.

14.8.2.5/9 говорит об этом:

Если P имеет форму, содержащую <T> или <i> [it does], то каждый аргумент Pi [Ts...] соответствующего списка аргументов шаблона P сравнивается с соответствующим аргументом Ai [int] соответствующего списка аргументов шаблона A. Если список аргументов шаблона P содержит расширение пакета, которое не является последним аргументом шаблона, весь список аргументов шаблона - это не выводимый контекст. [расширение пакета является последним, поэтому предыдущий не применяется] Если Pi - это расширение пакета [Ts..., это], то шаблон Pi сравнивается с каждым оставшимся аргументом в списке аргументов шаблона A (int). Каждое сравнение выводит шаблонные аргументы для последующих позиций в пакетах шаблонов параметров, расширенных с помощью Pi.

So class... Ts следует выводить как список элементов int, и, следовательно, шаблон функции должен быть создан с типом параметра const foo<int>& и быть жизнеспособным.

Это ошибка компилятора. Ваш код хорошо сформирован.

Более лаконично это хорошо сформировано:

template<class A, class... B> struct S { };

template<class... C> void f(S<C...>) { }

int main() { f(S<int>()); }

но с ошибкой аналогично не менее gcc 4.7.2 с:

 error: parameter 1 of ‘void f(S<C ...>) [with C = {int, C}]’
        has incomplete type ‘S<int, C>’

C неверно выводится как C = {int, C} (бессмысленная рекурсия) вместо C = {int}. Сломанный вывод C приводит к дальнейшему мусору, который S<int, C> имеет неполный тип.

Ответ 2

Ничего себе, я бы подумал, что это уже исправлено, но оно все еще не работает в предварительных сборках GCC 4.9 и Clang 3.4 (любезно Coliru).

Обходной путь прост: используйте частичную специализацию для вывода аргументов шаблона в другом месте.

template<class... Ts>
struct foo; // unimplemented

template<class T, class... Ts>
struct foo< T, Ts ... > // specialization for at least one argument
{ /* ... */ };

template< class... Ts >
std::ostream& operator<<( std::ostream& os, const foo<Ts...>& )
{
  return os << 42;
}

Почему и GCC, и Clang не могут решить эту летнюю ошибку, подражая обходному пути в общем случае, я не знаю. Поставщики компиляторов, возможно, сталкиваются с неудачным выбором между эффективностью и правильностью.