Search code examples
c++templateslanguage-lawyerc++17default-parameters

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).

Solution

  • 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.