Шаблонные псевдонимы 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, Clang говорит 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 выбирает первый тип из данной пачки, а затем тест переопределяется как 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...>

До сих пор мнения говорят, что это действительно, но сейчас, похоже, три компилятора не согласны. Итак, вопрос снова: это действительно?

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

13

Решение

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

Все, что нужно, это следующие определения:

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

Другие решения

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

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

0

По вопросам рекламы [email protected]