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

Алиасы шаблонов Variadic как аргументы шаблона (часть 2)

Это продолжение другого вопроса. Это относится к той же проблеме (я надеюсь), но использует совершенно другой пример, чтобы проиллюстрировать ее. Причина в том, что в предыдущем примере только экспериментальный GCC 4.9 завершился с ошибкой компилятора. В этом примере также Clang и GCC 4.8.1 работают по-разному: Clang создает неожиданный результат, а GCC 4.8.1 сообщает о другом сообщении об ошибке.

Ответы на предыдущий вопрос говорят более или менее, что код действителен, и проблема связана с экспериментальной версией GCC. Но этот результат меня немного скептически. Меня беспокоят месяцы с проблемами, которые, как я подозреваю, связаны (или одинаковыми), и это первый случай, когда у меня есть небольшой конкретный пример для иллюстрации.

Итак, вот какой-то код. Во-первых, некоторый общий код, который применяет SFINAE к произвольному тесту, указанному в метаданных альтернативного шаблона шаблона F:

#include <iostream>
using namespace std;

using _true  = integral_constant <bool, true>;
using _false = integral_constant <bool, false>;

template <typename T> using pass = _true;

template <template <typename...> class F>
struct test
{
    template <typename... A> static _false           _(...);
    template <typename... A> static pass <F <A...> > _(int);
};

template <template <typename...> class F, typename... A>
using sfinae = decltype(test <F>::template _<A...>(0));

Во-вторых, конкретный тест, проверяющий, задал ли данный класс тип с именем type:

template <typename T> using type_of  = typename T::type;
template <typename T> using has_type = sfinae <type_of, T>;

Наконец, пример:

struct A { using type = double; };

int main()
{
    cout << has_type <int>() << ", ";
    cout << has_type <A>()   << endl;
}

Ожидаемый результат будет 0, 1. Кланг говорит 0, 0. GCC 4.8.1 говорит

tst.cpp: In substitution of ‘template<class T> using type_of = typename T::type [with T = A ...]’:
tst.cpp:15:51: required from ‘struct test<type_of>’
tst.cpp:19:67: required by substitution of ‘template<template<class ...> class F, class ... A> using sfinae = decltype (test:: _<A ...>(0)) [with F = type_of; A = {T}]’
tst.cpp:24:58: required from here
tst.cpp:23:56: error: ‘A ...’ is not a class, struct, or union type
  template <typename T> using type_of = typename T::type; 
                                                        ^

и GCC 4.9 говорит

tst.cpp:19:67:   required by substitution of ‘template<template<class ...> class F, class ... A> using sfinae = decltype (test:: _<A ...>(0)) [with F = type_of; A = {T}]’
tst.cpp:24:58:   required from here
tst.cpp:15:51: error: pack expansion argument for non-pack parameter ‘T’ of alias template ‘template<class T> using type_of = typename T::type’
  template <typename... A> static pass <F <A...> > _(int);
                                                   ^

(номера строк могут отличаться). Итак, все терпит неудачу, по-разному.

Теперь, обходной путь. Metafunction car выбирает первый тип fom для данного пакета, а затем тест переопределяется как type_of2, теперь является переменным:

template <typename... T> struct car_t;
template <typename... T> using  car = type_of <car_t <T...> >;

template <typename T, typename... Tn>
struct car_t <T, Tn...> { using type = T; };

template <typename... T> using type_of2  = typename car <T...>::type;
template <typename T>    using has_type2 = sfinae <type_of2, T>;

int main()
{
    cout << has_type2 <int>() << ", ";
    cout << has_type2 <A>()   << endl;
}

Теперь все три компилятора говорят 0, 1, как и ожидалось. Интересно, что для любой версии GCC нам нужно удалить has_type (даже если мы его не используем) и оставить только has_type2; в противном случае мы имеем аналогичную ошибку.

Завершить. Я вижу проблему с одним шаблоном, ожидающим вариационный шаблон-параметр формы

template <typename...> class F

где мы фактически приводим в качестве входного значения невариантный шаблонный псевдоним формы

template <typename T> using alias = // ... anything including T or not

и, наконец, вызвать F, как если бы он был переменным:

F <A...>

Мнения пока говорят, что это действительно так, но теперь кажется, что три компилятора не согласны. Таким образом, вопрос снова: он действителен?

Для меня это важно, потому что у меня есть десятки файлов существующего кода, основанных на предположении, что это действительно так, и теперь мне нужен редизайн в любом случае (так как есть практические проблемы с этими компиляторами), но точная редизайн будет зависеть от ответ.

4b9b3361

Ответ 1

Это не отвечает на вопрос, действительно ли приведенный выше код, но является довольно довольно обходным решением, которое я нашел, экспериментируя вскоре после вопроса, и я считаю полезным поделиться.

Все, что необходимо, следующие определения:

template <template <typename...> class F>
struct temp { };

template <typename... A, template <typename...> class F>
F <A...> subs_fun(temp <F>);

template <template <typename...> class F, typename... A>
using subs = decltype(subs_fun <A...>(temp <F>()));

тогда везде, где F <A...> будет проблематичным, замените его на subs <F, A...>. Это. Я не могу объяснить, почему, но он работал во всех случаях до сих пор.

Например, в примере SFINAE вопроса просто замените строку

template <typename... A> static pass <F <A...> > _(int);

по

template <typename... A> static pass <subs <F, A...> > _(int);

Это изменение только в одной точке, весь оставшийся код остается прежним. Вам не нужно переопределять или обертывать все метафоры шаблонов, которые будут использоваться как F. Здесь живой пример.

Если F <A...> действительно действительно и компиляторы поддерживают его в конце концов, его снова легко отключить, поскольку изменения минимальны.

Я считаю это важным, потому что он позволяет указать тест SFINAE всего за две строки

template <typename T> using type_of  = typename T::type;
template <typename T> using has_type = sfinae <type_of, T>;

и является полностью общим. Как правило, для каждого такого теста требуется не менее 10 строк кода, а реализации <type_traits> полны такого кода. В некоторых случаях такие кодовые блоки определяются как макросы. С помощью этого решения шаблоны могут выполнять задание, а макросы не нужны.

Ответ 2

Я думаю, что ситуация довольно хорошо стандартизирована; С++ 11 14.3.3/1 говорит:

Аргумент шаблона шаблона-параметра шаблона должен быть именем шаблона класса или шаблона псевдонима, выраженным как id-expression.