`apply` шаблон компилируется в g ++, но не в clang ++ и vStack Overflow

Следующий код успешно компилируется в g ++ 7.2.0 (флаги компиляции -std=c++14 -Wall -Wextra -Werror -pedantic-errors), но он не компилируется в clang ++ 5.0.0 (с такими же флагами, -std=c++14 -Wall -Wextra -Werror -pedantic-errors) а также vc ++ 15.4 (флаги компиляции /EHsc /Za /std:c++14 /permissive-):

template <template <typename...> class Functor, typename... FixedArguments>
struct apply
{
template <typename... FreeArguments>
using type = Functor<FixedArguments..., FreeArguments...>;
};

template <typename, typename>
struct Bar{};

template <template <typename...> class>
struct Foo{};

int main()
{
(void)Foo<apply<Bar, int, char>::type>{};
}

Какое поведение компилятора соответствует стандарту? Как такой шаблон apply может быть изменено для компиляции на лязг ++, тоже?

лязг ++ Сообщения об ошибках:

5 : <source>:5:15: error: too many template arguments for class template 'Bar'
using type = Functor<FixedArguments..., FreeArguments...>;
^                          ~~~~~~~~~~~~~~~~~
16 : <source>:16:15: note: in instantiation of template class 'apply<Bar, int, char>' requested here
(void)Foo<apply<Bar, int, char>::type>{};
^
9 : <source>:9:8: note: template is declared here
struct Bar{};

VC ++ Сообщения об ошибках:

5 : <source>(5): error C2977: 'Bar': too many template arguments
9 : <source>(9): note: see declaration of 'Bar'
16 : <source>(16): note: see reference to class template instantiation 'apply<Bar,int,char>' being compiled

2

Решение

нота: посмотрев на это, этот ответ будет правильным, если Bar будет шаблоном псевдонима, а не шаблоном класса. Обходной путь работает, но по другим причинам. См. Конструкторы ответ для правильного ответа ОП.

Эта проблема известна как «недостаток псевдонима», и у нас было много проблем с ней при реализации kvasir :: mpl. Проблема в том, что Bar принимает ровно два параметра, но sizeof...(FixedArguments)+sizeof...(FreeArguments) может сложить что-то кроме 2.

Компилятор может либо попытаться отследить потенциальную арность через все вызовы псевдонимов, и выдавать ошибки только тогда, когда пользователь фактически пропускает что-то, кроме 2, или он может «охотно» выдавать ошибку, просто доказывая, что ошибка может произойти.

Обходной путь, который я нашел эффективным для решения этой проблемы, состоит в том, чтобы сделать вызов псевдонима зависимым от размера ввода. https://godbolt.org/g/PT4uaE

template<bool>
struct depends{
template<template<typename...> class F, typename...Ts>
using f = F<Ts...>;
};

template<>
struct depends<false>{
template<template<typename...> class F, typename...Ts>
using f = void;
};

template <template <typename...> class Functor, typename... FixedArguments>
struct apply
{
template <typename... FreeArguments>
using type = typename depends<(sizeof...(FixedArguments)+sizeof...(FreeArguments) == 2)>::template f<Functor, FixedArguments..., FreeArguments...>;
};

template <typename, typename>
struct Bar{};

template <template <typename...> class>
struct Foo{};

int main()
{
(void)Foo<apply<Bar, int, char>::type>{};
}

Следует отметить, что ограничение на ровно два не требуется для всех протестированных мною компиляторов, так же легко можно sizeof...(FixedArguments)+sizeof...(FreeArguments) != 100000 и компилятор все равно будет воспринимать его, только выдавая ошибку, если на конкретном вызове ничего не получится.

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

5

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

Как @ T.C. отметил в комментариях к вопросу такой код неверен (диагностика не требуется).

Стандарт C ++ 14, раздел «Разрешение имен» [temp.res], пункт 8:

Если каждая действительная специализация вариационного шаблона требует пустой
пакет параметров шаблона, шаблон плохо сформирован, нет диагностики
требуется.

Последние проекты стандарта C ++, раздел «Разрешение имен» [temp.res], пункт 8.3:

…Программа некорректна, диагностика не требуется, если:

  • каждая действительная специализация шаблона с переменными параметрами требует пустой пакет параметров шаблона …

Дополнительная информация: Основной выпуск 2067.

В соответствии со стандартными требованиями такой простой обходной путь может быть записан:

template <template <typename...> class Functor, typename... Arguments>
struct invoke
{
using type = Functor<Arguments...>;
};

template <template <typename...> class Functor, typename... FixedArguments>
struct apply
{
template <typename... FreeArguments>
using type = typename invoke<Functor, FixedArguments..., FreeArguments...>::type;
};

Живая демо

Обновить: Как @odinthenerd отметил в комментариях этот обходной путь использует дополнительный тип, который приводит к более медленной компиляции программы.

1

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