3
 #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.

Geezer
  • 5,600
  • 18
  • 31
Zeeshan Akhter
  • 476
  • 4
  • 15

3 Answers3

4

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

Abigail
  • 151
  • 4
  • It seems to be the case, but can you specify the source for this statement. – Zeeshan Akhter Aug 28 '18 at 05:23
  • [Default arguments](https://en.cppreference.com/w/cpp/language/default_arguments) "Default arguments are used in place of the missing trailing arguments in a function call" so the default args are only ever used when x is missing – Abigail Aug 28 '18 at 05:45
  • What you said below comes into play after default args are worked out. "If a function template f is called in a way that requires a default argument to be used" The OP wants to know why default arguments are not used to force the type of T into a string *always*. The answer is because default arguments only ever come into play when used in place of trailing args in a function call. So what you posted is why the error is caused, but it's not why T is not a string always. – Abigail Aug 28 '18 at 05:56
  • Nop. What you said implies there's already a function. But this is only a *function template*. So logical precedence is for the instantiation in this case. – Geezer Aug 28 '18 at 06:11
3

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.

Geezer
  • 5,600
  • 18
  • 31
  • "It only means that std::string is the default argument for f() in case the call is made without any arguments." You mean `f()` at call site – Zeeshan Akhter Aug 28 '18 at 05:29
  • @ZeeshanAkhter exactly friend. – Geezer Aug 28 '18 at 05:30
  • @ZeeshanAkhter I've edited to make it clearer for you. Let me know if it's still unclear. – Geezer Aug 28 '18 at 05:33
  • for `f()`, you said "it will cause an instantiation of f() and the value of x will be an empty string", but type-parameter is not deduced by its default argument, unless you make it default value to type-parameter. FYI https://stackoverflow.com/a/9629009/6367733. So just writing `f()` will fail in this case. – Zeeshan Akhter Aug 28 '18 at 13:19
  • I ran the code against c++17 http://coliru.stacked-crooked.com/a/30e259f411b8444d , but still fails. – Zeeshan Akhter Aug 28 '18 at 13:27
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/178938/discussion-between-skepticalempiricist-and-zeeshan-akhter). – Geezer Aug 28 '18 at 13:29
0

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.

lubgr
  • 37,368
  • 3
  • 66
  • 117