I recently discovered a feature of templates that I was unaware of. It is the fusion of different declarations of the same template with different default arguments in the template arguments, it is detailed in the C++ standard (my translation):
17.1 template parameters
The set of available default template arguments is obtained by merging the default arguments of all previous template declarations in the same way as the default arguments of functions [ Example:
template<class T1, class T2 = int> class A; template<class T1 = int, class T2> class A;
It is equivalent to
template<class T1 = int, class T2 = int> class A;
— end of example ]
So I've decided to try the example that the standard mentions:
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 above code shows void S<int, int>::f() [T = int, U = int]
when compiled with gcc HEAD 8.0.1 201803 but the compilation fails with clang HEAD 7.0.0 unless angle brackets are used when instantiating:
S s; s.f(); // error: declaration of variable 's' with deduced type 'S' requires an initializer
S<> t; t.f(); // Correcto
Leaving aside this error, I have tested this behavior in the rest of the template types and the code is accepted or rejected in an irregular way:
Funciones plantilla
template <typename T, typename U = int> void function();
template <typename T = int, typename U> void function()
{ std::cout << __PRETTY_FUNCTION__ << '\n'; }
int main()
{
/* Rechazado por GCC: no matching function for call to 'function()'
template argument deduction/substitution failed:
couldn't deduce template parameter 'T'
mismo error con function<>()
CLang compila sin problemas. */
function(); // CLang muestra 'void function() [T = int, U = int]'
return 0;
}
Variable plantilla
template <typename T, typename U = int> int variable;
template <typename T = int, typename U> int variable = 0;
int main()
{
/* GCC se queja del número de argumentos (0, should be at least 1)
mientras que CLang se queja de la redefinición de 'variable' */
std::cout << variable<> << '\n';
return 0;
}
Alias plantilla
template <typename T, typename U = int> using alias = int;
template <typename T = int, typename U> using alias = int;
int main()
{
/* GCC se queja de la redefinición de 'alias'
mientras que CLang compila sin problemas. */
alias<> v = 0;
std::cout << v << '\n';
return 0;
}
The standard text doesn't differentiate between template types, so I figured all types should behave the same.
Still, the case of the template variable is the only one rejected by both compilers, so I have some doubts about this type of template. It makes sense to me that CLang rejects the template on the grounds of redefinition while I think GCC is wrong to reject the template for the wrong reasons , but this reasoning does not follow what the standard says in 17.1.10.
So, what should you expect in the case of template variables:
- The code is rejected by redefinition (CLang is right).
- The code is accepted, merging all the definitions (both GCC and CLang get it wrong).
In the absence of a better explanation, the most accurate thing I can say is that, from my point of view, the behavior of CLang is as expected. I explain:
In C++ it is not possible to redeclare variables. Template variables are, in essence, variables, so it is understandable that they have to satisfy the limitations imposed by " traditional " variables .
In this case we have what would clearly be a restatement:
Another song would happen if they were specializations:
To make matters worse, after some tests, I have observed that accompanying the first declaration with
extern
Clang shuts up and buys the solution:Which makes some sense since with
extern
you're not declaring a variable anywhere but the variable has to already exist. In fact, this same solution would be applicable without templates (although then it doesn't make sense at all):