2

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
Constructor
  • 7,273
  • 2
  • 24
  • 66
  • What error does Clang give? – piwi Nov 22 '17 at 10:30
  • @piwi *clang++* and *vc++* error messages are available at [the link to the **godbolt.org** in the question](https://godbolt.org/g/XeaEpr). I have added them to the question, too. – Constructor Nov 22 '17 at 10:35
  • My bad did not see it before posting the comment. – piwi Nov 22 '17 at 10:39
  • Add a 3rd template parameter `typename...` to `Bar`? – songyuanyao Nov 22 '17 at 10:42
  • @songyuanyao `Bar` and `Foo` are the example classes here to demonstrate the problem. Their characteristics are given from outside. In fact it is the smallest MWE that demonstrates the problem in some complex template code. – Constructor Nov 22 '17 at 11:03
  • I cannot tell which is compliant but to me the error seems relevant; when deducing the `apply::type` template alias, `Bar` is fully specialized, therefore there is no way additional types can be fed to `Functor` later on. – piwi Nov 22 '17 at 11:17
  • 1
    [\[temp.res\]/8.3](https://timsong-cpp.github.io/cppwp/temp.res#8.3). This is ill-formed NDR. But see [core issue 2067](https://wg21.link/CWG2067). – T.C. Nov 22 '17 at 11:23
  • 1
    @T.C. Thank you for your references! But how such code can be fixed to be compiled? – Constructor Nov 22 '17 at 11:33

2 Answers2

5

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.

odinthenerd
  • 5,422
  • 1
  • 32
  • 61
1

As @T.C. 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.

Constructor
  • 7,273
  • 2
  • 24
  • 66