8

Why the overload resolution for the call max(x, y) in the expression return max(max(x, y), z); below results in a call to the non-template function char const* max(char const*, char const*) ?

As far as I can understand, the function max<const char*>(x, y) is a better fit than the former, as x is a const char* const& and y is a const char* const& !

#include <iostream>

template <typename T>
T const& max (T const& x, T const& y)
{
    return x < y ? y : x;
}

char const* max (char const* x, char const* y)
{
    return std::strcmp(x, y) < 0 ? y : x;
}

template <typename T>
T const& max (T const& x, T const& y, T const& z)
{
    return max (max(x, y), z);
}

int main ()
{
    const char* sx = "String_x";
    const char* sy = "String_y";
    const char* sz = "String_z";
    max(sx, sy, sz);
}
Belloc
  • 6,318
  • 3
  • 22
  • 52

1 Answers1

4

Why the overload resolution for the call max(x, y) in the expression return max(max(x, y), z); below results in a call to the non-template function char const* max(char const*, char const*)?

When invoking this function:

template <typename T>
T const& max (T const& x, T const& y, T const& z)
{
    return max (max(x, y), z);
}

T is deduced to be const char*. Therefore, this signature is instantiated:

const char* const& max (
    const char* const& x, 
    const char* const& y, 
    const char* const& z
    )

The function internally calls the binary version of max() with arguments of type const char*. Both the template and the non-template overload are viable for an argument of type const char*.

However, when two functions are viable for resolving the call and one of them is not a template, the non-template version is considered a best fit.

Per Paragraph 13.3.3/1 of the C++11 Standard:

Given these definitions,** a viable function F1 is defined to be a better function than another viable function F2 if** for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then

— for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,

— the context is an initialization by user-defined conversion (see 8.5, 13.3.1.5, and 13.3.1.6) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type. [ ... ] or, if not that,

F1 is a non-template function and F2 is a function template specialization, or, if not that,

— F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 14.5.6.2.

This explains why the non-template overload is picked.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • There is no conversion for the template function – Belloc Mar 23 '13 at 12:12
  • @user1042389: Not even for the non-template function – Andy Prowl Mar 23 '13 at 12:13
  • `The function internally calls the binary version of max() with arguments of type const char*` I don't agree with this. The function `max()` is called with arguments of type `const char* const&`. – Belloc Mar 23 '13 at 12:14
  • @user1042389: The non-template function takes its arguments by value, so no conversion is required – Andy Prowl Mar 23 '13 at 12:15
  • There must be a conversion from `const char* const&` to `const char*` in this case. – Belloc Mar 23 '13 at 12:16
  • @user1042389: See [this](http://liveworkspace.org/code/41HfPq$13): it doesn't compile, showing that the function taking by value and the function taking by reference to `const` are equally good. – Andy Prowl Mar 23 '13 at 12:19
  • It doesn't compile exactly because the non-template function is being called instead of the other. – Belloc Mar 23 '13 at 12:23
  • @user1042389: They are both non-template functions in my example. Mind the message *"error: call to 'max' is ambiguous"*. This means those overloads are equally good. Which means that when one is made a template, because of the paragraph I quoted, the non-template version is picked. – Andy Prowl Mar 23 '13 at 12:25
  • 1
    `This means those overloads are equally good` That's what I'm trying to understand. As I said before, to use the non-template function there a conversion, whereas for the template function the match is perfect. – Belloc Mar 23 '13 at 12:27
  • @user1042389: Look, if that was the case, then even in my example the `const&` version would be picked, right? Instead, the call is ambiguous, because both are equally good. – Andy Prowl Mar 23 '13 at 12:29
  • @Andy: I think OP's wondering _why_ these two non-templates are equaly good, yet not redefinitions? Why doesn't the `const&` matter during overload resolution, but makes for two different non-template functions anyway? – Mat Mar 23 '13 at 12:32
  • @Mat: I think I explained that: because no conversion is required in the second case as well - redefinition is excluded because the signature is different. – Andy Prowl Mar 23 '13 at 12:34
  • @AndyProwl In VS2010 the code executes and crashes because the non-template function is chosen. There's no ambiguity here. – Belloc Mar 23 '13 at 12:37
  • 1
    @user1042389: Of course the non-template function is chosen, that's what I explained in my answer. There **is** ambiguity [in my example](http://liveworkspace.org/code/41HfPq%2413), but again: if one of the two functions is made a template, the ambiguity is resolved by picking the **non-template** version. This is what my answer tells. – Andy Prowl Mar 23 '13 at 12:40
  • @user1042389: [Yet another example](http://liveworkspace.org/code/41HfPq$14). – Andy Prowl Mar 23 '13 at 12:42
  • 1
    @AndyProwl I've just realized that in your first example you have changed the code. But my question is the same as before. Considering your code, why would there be an ambiguity given that `max(const char* const&, const char* const&)` is a better fit for the call `max(x, y)` than `max(const char*, max const char*)`, as the former is a perfect match and the last requires a conversion ? – Belloc Mar 23 '13 at 12:50
  • @user1042389: Because your premise is incorrect: it is not a better fit. Now if you're wondering why it is not a better fit, then the reason is that no conversion is required in both cases. [My last example](http://liveworkspace.org/code/41HfPq%2414) is trying to make this evident: when you pass `y` to `foo(int)`, the fact that `y` is a `const &` to `x` doesn't mean a conversion is required. – Andy Prowl Mar 23 '13 at 12:54
  • @user1042389: Maybe my last point was not completely correct: a qualification adjustment is needed (from `const int` to `int`), but that counts as an exact match. – Andy Prowl Mar 23 '13 at 12:58
  • @AndyProwl In your second example, to call to non-template function `f(int)` there is a conversion from `const int&` to `int`, whereas to call the template function `f(int const&)` there is no conversion. So, AFAIK again the template should be chosen. – Belloc Mar 23 '13 at 13:00
  • @AndyProwl There must be something in the standard that supports the current behavior. That's what I'd like to see. I don't understand much about the standard though. – Belloc Mar 23 '13 at 13:04
  • 1
    @user1042389: You can disregard the `&` for the *argument*, because when the expression is evaluated, the `&` is ignored - see Paragraph 5/6: *"If an expression initially has the type “reference to T” (8.3.2, 8.5.3), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression."*. So what remains is a qualification adjustment from `const int` to `int`, and this counts as an Exact Match (see Table 12 of Paragraph 13.3.3.1.1/3) – Andy Prowl Mar 23 '13 at 13:04
  • @AndyProwl `You can disregard the & for the argument, because when the expression is evaluated, the & is ignored` That's not what I observe when I look into the assembly generated by the compiler. When a formal argument to a function is a `const char* const&`, the value passed to the function is an address in the stack that contains the address of the string literal passed to the function. How come the & is ignored ? – Belloc Mar 23 '13 at 14:00
  • @user1042389: How concretely the compiler decides to pass the argument is outside the scope of the C++ language, it is potentially implementation-dependent, and based on considerations which are not relevant when analyzing the source code. As long as the semantics of the program as prescribed by the C++ Standard is respect, the compiler is free to do anything it wishes. – Andy Prowl Mar 23 '13 at 14:56
  • @AndyProwl +1 Paragraph 5 in `5 Expressions` and `13.3 Overload resolution` are so far apart in the Standard that it took me sometime to see that your answer was correct. Thanks for taking your time. – Belloc Mar 25 '13 at 13:57