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.