Data type of default value for template function argument different from instantiating data type

Viewed 205
 #include <iostream>
 #include <string>

 template <typename T>
 void f(T x = std::string{""})
 {
   std::cout << '[' << x << ']' << std::endl;
 }

 int main()
 {
   f(23); // Case 1: Doesn't fail
   f<int>(); // Case 2: Compilation error
   f<int>(23); // Case 3: Doesn't fail
 }

Shouldn't the Case 1 and Case 3 also fail, because the function template is instantiated by int and the deafult value is of type std::string.

3 Answers

T x = std::string{""} is only executed when no arg is given for x.

f(23)

implicit instantiation to f<int>. default value for x not used because 23 is supplied as an int literal.

f<int>()

now T is of type int but you are assigning to x with a std::string

f<int>(23)

T is still int, same as case 1

Shouldn't the Case 1 and Case 3 also fail, because the function template is instantiated by int and the deafult value is of type std::string?

No it shouldn't. See below for why. If you want it to fail then you should define f() to be a regular function receiving an argument of type std::string and not a function template.

What you have is a case of default argument instantiation, see here in [temp.inst]:

If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation [...] is done as if the default argument had been an initializer used in a function template specialization, [...] This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f.

When you write:

template <typename T>
void f(T x = std::string{""}){...}

it means that "" is the default argument for x only in case the call is made without any arguments. for example in your Case 2.

Your template function is defined to be instatiated just fine for either int or various other types.

For example here (Case 1):

f(23);

It is instatiated implicitly (by inference base on the argument type) to be f<int>(), as 23 is defined in the spec to be an int literal. The parameter x of type int receives the non-default value of 23 which you have provided at the call site.

And here (Case 2):

f<int>();

is exactly where the standard clause above comes into play: You successfully instantiate f() but no argument is provided for it, hence the instantiated default argument is then used as the argument of f. So compilation fails as this default value here for parameter x is defined to just be std::string and no conversion applies from it to type int. It is effectively equivalent to calling f("") on a function declared void f(int x).

And here (Case 3):

f<int>(23);

Explicit again and this time you provide the right type of argument at the call site.

The answer provided by @Abigail explains why case 1 and 3 do not fail, let me additionally point out that the function template doesn't make that much sense. A function with one parameter that has a default value is expected to be callable like this:

void g(std::string = "") { /* ... */ }

g(); // Use default parameter
g("non-default");

In contrast, your function template can be called with a given parameter

f("non-default");

but not without any parameter, because the compiler does not deduce template types from default arguments.

f(); // Doesn't compile

I would suggest changing the template to

 template <typename T = std::string>
 void f(T x = T{})
 {
     // same as before
 }

which fixes the f() instantiation.

Related