flatmouse flatmouse - 1 month ago 17
C++ Question

Template default type vs default value

This question follows a previous one.




Case 1: Default type



The following program does not compile and reports
error C2995: 'T foo(void)': function template has already been defined
:

#include <iostream>
#include <type_traits>

template < typename T, typename = std::enable_if_t< std::is_integral<T>::value> >
T foo() { std::cout << "integral" << std::endl; return T(); }

template < typename T, typename = std::enable_if_t< !std::is_integral<T>::value> >
T foo() { std::cout << "non-integral" << std::endl; return T(); }

int main() {
foo<int>();
foo<float>();
}


Each of the templates are alternately used and ignored (SFINAE), by the two
foo
instantiations. So I assume the compiler at some point sees:

template < typename T, typename = void >
T foo() { std::cout << "integral" << std::endl; return T(); }

template < typename T, typename = void >
T foo() { std::cout << "non-integral" << std::endl; return T(); }


Both definitions are the same and the error is somewhat understandable. Maybe less understandable is why the compiler hadn't assigned different internal function names by this point.




Case 2: Default value



Now, the program can be fixed by, rather than using default types, we use default values:

template < typename T, std::enable_if_t< std::is_integral<T>::value>* = nullptr >
T foo() { std::cout << "integral" << std::endl; return T(); }

template < typename T, std::enable_if_t< !std::is_integral<T>::value>* = nullptr >
T foo() { std::cout << "non-integral" << std::endl; return T(); }


Here, following the same procedure, I derive:

template < typename T, void* = nullptr >
T foo() { std::cout << "integral" << std::endl; return T(); }

template < typename T, void* = nullptr >
T foo() { std::cout << "non-integral" << std::endl; return T(); }


Which, had that been the substitution, would have had the same definition and would not have compiled. So clearly the compiler is not doing that, or if it is, it doesn't stop there and ends up with something like:

int foo_int() { std::cout << "integral" << std::endl; return int(); }

float foo_float() { std::cout << "non-integral" << std::endl; return float(); }

int main() {
foo_int();
foo_float();
}





Why does the compiler manage to get two different functions in the second case, but not the first?

What algorithm does the standard specify for interpreting template default types vs. default values?

Answer

Here, following the same procedure, I derive:

Everything was right until that point. You have your two function templates (ignoring the defaults):

template < typename T, std::enable_if_t< std::is_integral<T>::value>*>
T foo();

template < typename T, std::enable_if_t< !std::is_integral<T>::value>*>
T foo();

The two non-type template parameters do not have type void*. They have type std::enable_if_t<std::is_integral<T>::value>* and std::enable_if_t<!std::is_integral<T>::value>*, respectively. Those aren't the same type. There doesn't even exist a T for which, after substitution, those are the same type.

The specific rule is in [temp.over.link]:

Two expressions involving template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one-definition rule (3.2), except that the tokens used to name the template parameters may differ as long as a token used to name a template parameter in one expression is replaced by another token that names the same template parameter in the other expression. For determining whether two dependent names (14.6.2) are equivalent, only the name itself is considered, not the result of name lookup in the context of the template.

Two function templates are equivalent if they are declared in the same scope, have the same name, have identical template parameter lists, and have return types and parameter lists that are equivalent using the rules described above to compare expressions involving template parameters. Two function templates are functionally equivalent if they are equivalent except that one or more expressions that involve template parameters in the return types and parameter lists are functionally equivalent using the rules described above to compare expressions involving template parameters. If a program contains declarations of function templates that are functionally equivalent but not equivalent, the program is ill-formed; no diagnostic is required.

These two functions don't have identical template parameter lists.