c++templateslanguage-lawyerusingdecltype

Using decltype for templated function declaration results in "conflict" when defining the templated function


I am using a decltype to ensure a template function force2 has the same parameter and return types as another template function force1 but it doesn't compile. I require the functions to be templated. Consider this code:

template <typename N>
void force1(N a);

template <typename N>
using ForceType = decltype(force1<N>);

template <typename N>
ForceType<N> force2;

template <typename N>
void force2(N a)
{}

This results in an error:

<source>:11:16: error: 'template<class N> void force2(N)' conflicts with a previous declaration
   11 | void force2(N a)
      |                ^
<source>:8:14: note: previous declaration 'template<class N> ForceType<N> force2<N>'
    8 | ForceType<N> force2;
      |              ^~~~~~
Compiler returned: 1

I expected this to compile: with force2 being a templated function with empty body. What is actually going on over here? The compiler hints that template<class N> void force2(N) is not the same as template<class N> ForceType<N> force2<N> which I am not able to understand. They definitely seem different on the first look but shouldn't the expansion of ForceType<N> result in template<class N> void force2(N)?

When I use a concrete type in declaration of force2:

template <typename N>
void force1(N a);

template <typename N>
using ForceType = decltype(force1<N>);

ForceType<int> force2;

void force2(int a)
{}

It compiles without errors.


Solution

  • ForceType<N> is a dependent type.

    [temp.spec.general]p8:

    If a function declaration acquired its function type through a dependent type without using the syntactic form of a function declarator, the program is ill-formed.

    ForceType<N> force2; does not meet this requirement, so this is ill-formed.

    In compilers, this will be parsed as a variable template since, because of the above rule, it cannot be a function template.

    Any attempt to actually use force2<N> will result in an error (https://godbolt.org/z/f3Ke3x8db)


    And even if you were to fix it by making it of the form of a function declaration:

    template<typename>
    struct unary_fn;
    template<typename Ret, typename Arg>
    struct unary_fn<Ret(Arg)> { using return_type = Ret; using argument_type = Arg; };
    
    template <typename N>
    void force1(N a);
    
    template <typename N>
    using ForceType = typename unary_fn<decltype(force1<N>)>::return_type(typename unary_fn<decltype(force1<N>)>::argument_type);
    
    template <typename N>
    ForceType<N> force2;
    
    template <typename N>
    void force2(N a)
    {}
    

    This would declare two separate templates overloads because ForceType<N> and void(N) are not equivalent (i.e., the latter is not a definition of the former)