Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template variables with template argument deduction and default template parameters

Amazed (and cofused) by a similar question I tried myself the example that the mentioned question found in the standard:

template <typename T, typename U = int> struct S;
template <typename T = int, typename U> struct S
{ void f() { std::cout << __PRETTY_FUNCTION__ << '\n'; } };

int main()
{
    S s; s.f();
    return 0;
}

The code above prints void S<int, int>::f() [T = int, U = int] compiled with gcc HEAD 8.0.1 201803 but fails to compile with clang HEAD 7.0.0 unless angle brackets are used during instantiation:

S s; s.f(); // error: declaration of variable 's' with deduced type 'S' requires an initializer
S<> t; t.f(); // Correct

This issue aside, I've checked the other template flavors for this particular behavior and the code is accepted or rejected in a pretty irregular manner:

Template function
template <typename T, typename U = int> void function();
template <typename T = int, typename U> void function()
{ std::cout << __PRETTY_FUNCTION__ << '\n'; }

int main()
{
    /* Rejected by GCC: no matching function for call to 'function()'
       template argument deduction/substitution failed:
       couldn't deduce template parameter 'T'
       same error with function<>()

       CLang compiles without issues */
    function(); // CLang prints 'void function() [T = int, U = int]'
    return 0;
}
Template variable
template <typename T, typename U = int> int variable;
template <typename T = int, typename U> int variable = 0;

int main()
{
    /* GCC complains about wrong number of template arguments (0, should be at least 1)
     while CLang complains about redefinition of 'variable' */
    std::cout << variable<> << '\n';
    return 0;
}
Template alias
template <typename T, typename U = int> using alias = int;
template <typename T = int, typename U> using alias = int;

int main()
{
    /* GCC complains about redefinition of 'alias'
       while CLang compiles just fine. */
    alias<> v = 0;
    std::cout << v << '\n';
    return 0;
}

The standards text about this feature doesn't tell apart the different template types so I thought that they should behave the same.

But yet, the template variable case is the one rejected by both compilers, so I have some doubts about the template variable option. It makes sense to me that CLang is right rejecting the template variable complaining about redefinition while GCC is wrong by rejecting the code for the wrong reasons, but this reasoning doesn't follow what the standard says in [temp.param]/10.

So what should I expect for the case of template variables?:

  • Code rejected due to redefinition (CLang is right).
  • Code accepted, merging both template definitions (Both GCC and CLang are wrong).
like image 491
PaperBirdMaster Avatar asked Mar 12 '18 11:03

PaperBirdMaster


People also ask

What is template argument deduction?

Template argument deduction is used in declarations of functions, when deducing the meaning of the auto specifier in the function's return type, from the return statement.

Can template have default parameters?

Template parameters may have default arguments. The set of default template arguments accumulates over all declarations of a given template.

What are template parameters?

In UML models, template parameters are formal parameters that once bound to actual values, called template arguments, make templates usable model elements. You can use template parameters to create general definitions of particular types of template.

What is a template template parameter in C++?

In C++ this can be achieved using template parameters. A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.


2 Answers

For class template argument deduction, this is a clang bug. From [temp.param]/14:

The set of default template-arguments available for use is obtained by merging the default arguments from all prior declarations of the template in the same way default function arguments are ([dcl.fct.default]). [ Example:

template<class T1, class T2 = int> class A;
template<class T1 = int, class T2> class A;

is equivalent to

template<class T1 = int, class T2 = int> class A;

— end example ]

When you write S s, both template arguments are defaulted, and so the rewrite of the default constructor is:

template <typename T=int, typename U=int>
S<T, U> __f();

Which is viable and should deduce S<int, int>.


For functions, you do not need to specify <> to call a function template if all the template parameters can be deduced. This is [temp.arg.explicit]/3:

If all of the template arguments can be deduced, they may all be omitted; in this case, the empty template argument list <> itself may also be omitted.

Note that this applies to deduction. There is no deduction for alias templates or variable templates. Hence, you cannot omit the <>. This is [temp.arg]/4:

When template argument packs or default template-arguments are used, a template-argument list can be empty. In that case the empty <> brackets shall still be used as the template-argument-list.

like image 108
Barry Avatar answered Oct 11 '22 00:10

Barry


Disclaimer: the following is valid in context of C++14. With C++17 both compilers are wrong. See the another answer by Barry.

Looking into details I see that Clang is correct here, while GCC is confused.

  • First case, class template (unlike function template) indeed requires <>.

  • Second case, function template, is treated by Clang exactly like the first case, without the syntactic requirement to use <> to indicate that template is used. This applies in C++ to function templates in all contexts.

  • Third case: as of variables, I see that Clang is correct while GCC is confused. If you redeclare the variable with extern Clang accepts it.

    template <typename T, typename U = int> int variable = 0;
    template <typename T = int, typename U> extern int variable;
    
    int main()
    {
        // accepted by clang++-3.9 -std=c++14
        std::cout << variable<> << '\n';
        return 0;
    }
    

    so it again behaves consistently both with standard and the previous cases. Without extern this is redefinition and it is forbidden.

  • Fourth case, using template. Clang again behaves consistently. I used typeid to ensure that alias is indeed int:

    template <typename T, typename U = int> using alias = int;
    template <typename T = int, typename U> using alias = int;
    
    int main()
    {
        alias<> v = 0;
        std::cout << v << '\n';
        std::cout << typeid(v).name() << '\n';
        return 0;
    }
    

    then

    $ ./a.out | c++filt -t
    

    outputs

    0
    int
    

So Clang indeed makes no difference of what kind of template is re-declared, as stated in the standard.

like image 31
Peter K Avatar answered Oct 11 '22 00:10

Peter K