Template variables with template argument deduction and default template parameters
Asked Answered
H

2

9

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).
Haya answered 12/3, 2018 at 11:24 Comment(7)
Have you tried to redeclare variable as extern? Otherwise it's indeed a redefinition.Doited
If this doesn't count as bullying compilers, I don't know what does ;)Alt
@PeterK I didn't. I thought that in the worst case it will behave as the alias template (both cases have initializer and it's accepted, at least, by CLang).Haya
@Paula_plus_plus, please see my answer below. With extern everything works in Clang.Doited
@PeterK thanks a lot! I'm already testing it.Haya
I realized that the question doesn't make clear... C++17 or C++14? Matters for the first case.Rabelais
@Rabelais I've tested all with the most recent compilers, so it'll be C++17.Haya
D
1

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.

Doited answered 12/3, 2018 at 12:34 Comment(2)
"First case, class template (unlike function template) indeed requires <>" no longer true in C++17.Acclaim
Sorry, I only have Clang 3.9 at hand, which only supports C++-14; I'll check with the newer version.Doited
R
3

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.

Rabelais answered 12/3, 2018 at 14:22 Comment(2)
And which compiler is right in case of template variable?Noach
@Noach Both? Neither compile it - which is correct. Clang's diagnostic is admittedly better.Rabelais
D
1

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.

Doited answered 12/3, 2018 at 12:34 Comment(2)
"First case, class template (unlike function template) indeed requires <>" no longer true in C++17.Acclaim
Sorry, I only have Clang 3.9 at hand, which only supports C++-14; I'll check with the newer version.Doited

© 2022 - 2024 — McMap. All rights reserved.