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

Следующий код успешно компилируется в 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 можно изменить, чтобы он компилировался и на clang++?

clang++ сообщения об ошибках:

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

person Constructor    schedule 22.11.2017    source источник
comment
Какую ошибку выдает Clang?   -  person piwi    schedule 22.11.2017
comment
Сообщения об ошибках @piwi clang++ и vc++ доступны по ссылке на godbolt.org в вопросе. Я тоже добавил их к вопросу.   -  person Constructor    schedule 22.11.2017
comment
Мой плохой не видел его до публикации комментария.   -  person piwi    schedule 22.11.2017
comment
Добавить третий параметр шаблона typename... в Bar?   -  person songyuanyao    schedule 22.11.2017
comment
@songyuanyao Bar и Foo — это классы-примеры, демонстрирующие проблему. Их характеристики даны извне. На самом деле это самый маленький MWE, который демонстрирует проблему в каком-то сложном коде шаблона.   -  person Constructor    schedule 22.11.2017
comment
Я не могу сказать, что совместимо, но мне ошибка кажется актуальной; при выводе псевдонима шаблона apply<Bar, int, char>::type Bar<int, char> является полностью специализированным, поэтому дополнительные типы не могут быть переданы в Functor позже.   -  person piwi    schedule 22.11.2017
comment
[temp.res]/8.3. Это неправильно сформированный отчет о недоставке. Но см. основной вопрос 2067.   -  person T.C.    schedule 22.11.2017
comment
@Т.С. Спасибо за ваши ссылки! Но как такой код исправить для компиляции?   -  person Constructor    schedule 22.11.2017


Ответы (2)


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

Эта проблема известна как «недостаток псевдонимов», и у нас было много проблем с ней при реализации 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 мы в настоящее время экспериментируем с отслеживанием арности вручную за кулисами, чтобы исключить зависимые вызовы, которые делают немного замедлить ход событий.

person odinthenerd    schedule 22.11.2017
comment
Спасибо. Не могли бы вы скопировать код обходного пути в свой ответ? - person Constructor; 22.11.2017

Как @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 отмечено в комментариях этот обходной путь использует дополнительный тип, который приводит к более медленной компиляции программы.

person Constructor    schedule 22.11.2017
comment
Я думаю, что это на самом деле правильный ответ, и мое решение просто работает. Следует отметить, однако, что обходной путь создает тип и, следовательно, будет медленнее. - person odinthenerd; 27.11.2017
comment
@odinthenerd Я добавил необходимое примечание. - person Constructor; 29.11.2017
comment
@odinthenerd Вы не возражаете, что я принимаю свой ответ вместо вашего? - person Constructor; 29.11.2017