简体   繁体   中英

`apply` template compiles in g++ but not in clang++ and vc++

The following code compiles successfully in g++ 7.2.0 (compilation flags are -std=c++14 -Wall -Wextra -Werror -pedantic-errors ), but it fails to compile in clang++ 5.0.0 (with the same flags, -std=c++14 -Wall -Wextra -Werror -pedantic-errors ) and vc++ 15.4 (compilation flags are /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>{};
}

Which compiler behavior is standard compliant? How such template apply may be changed to be compiled on clang++ , too?

clang++ error messages:

 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++ error messages:

 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 

note: after looking at this, this answer would be correct if Bar were an alias template rather than a class template. The workaround works but for other reasons. See Constructors answer for the correct answer of the OP.

This problem is known as the 'alias flaw' and we had a lot of challenges with it in the implementation of kvasir::mpl. The problem is that Bar takes exactly two parameters but sizeof...(FixedArguments)+sizeof...(FreeArguments) could add up to something besides 2.

The compiler can either try and track the potential arity through all the alias calls and only issue errors when the user actually passes something besides 2 or it can "eagerly" give an error simply by proving that an error could occur.

The work around I have found effective in dealing with this is to make the alias call dependant on the size of the input 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>{};
}

It should be noted that constraining to exactly two is not needed on all compiler I have tested, one could just as easilty constrain to be sizeof...(FixedArguments)+sizeof...(FreeArguments) != 100000 and the compiler will still take it only issuing an error if things actually don'T work out on a concrete call.

I would actually like to improve my mental model of how this works internally in order to come up with faster work arounds, in kvasir::mpl we are currently experimenting with tracking the arity manually behind the scenes in order to eliminate the dependant calls which do slow things down a little.

As @TC noted in the comments to the question such code is ill-formed (no diagnostic required).

C++14 standard, section "Name resolution" [temp.res], paragraph 8:

If every valid specialization of a variadic template requires an empty template parameter pack, the template is ill-formed, no diagnostic required.

Latest drafts of the C++ standard, section "Name resolution" [temp.res], paragraph 8.3:

...The program is ill-formed, no diagnostic required, if:

  • ...
  • every valid specialization of a variadic template requires an empty template parameter pack...

Additional information: Core Issue 2067 .

In accordance with the standard requirements such simple workaround can be written:

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;
};

Live demo

Update: As @odinthenerd noted in the comments this workaround uses an additional type which leads to a slower compilation of the program.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM