Why does pointer decay take priority over a deduced template?

C++ArraysOverload Resolution

C++ Problem Overview


Let's say I'm writing a function to print the length of a string:

template <size_t N>
void foo(const char (&s)[N]) {
    std::cout << "array, size=" << N-1 << std::endl;
}

foo("hello") // prints array, size=5

Now I want to extend foo to support non-arrays:

void foo(const char* s) {
    std::cout << "raw, size=" << strlen(s) << std::endl;
}

But it turns out that this breaks my original intended usage:

foo("hello") // now prints raw, size=5

Why? Wouldn't that require an array-to-pointer conversion, whereas the template would be an exact match? Is there a way to ensure that my array function gets called?

C++ Solutions


Solution 1 - C++

The fundamental reason for this (standard-conforming) ambiguity appears to lie within the cost of conversion: Overload resolution tries to minimize the operations performed to convert an argument to the corresponding parameter. An array is effectively the pointer to its first element though, decorated with some compile-time type information. An array-to-pointer conversion doesn't cost more than e.g. saving the address of the array itself, or initializing a reference to it. From that perspective, the ambiguity seems justified, although conceptually it is unintuitive (and may be subpar). In fact, this argumentation applies to all Lvalue Transformations, as suggested by the quote below. Another example:

void g() {}

void f(void(*)()) {}
void f(void(&)()) {}

int main() {
    f(g); // Ambiguous
}

The following is obligatory standardese. Functions that are not specializations of some function template are preferred over ones that are if both are otherwise an equally good match (see [over.match.best]/(1.3), (1.6)). In our case, the conversion performed is an array-to-pointer conversion, which is an Lvalue Transformation with Exact Match rank (according to table 12 in [over.ics.user]). [over.ics.rank]/3:

> - Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if > - S1 is a proper subsequence of S2 (comparing the conversion sequences in the canonical form defined by 13.3.3.1.1, excluding > any Lvalue Transformation; the identity conversion sequence is considered to be a subsequence of any non-identity conversion > sequence) or, if not that, > > - the rank of S1 is better than the rank of S2, or S1 and S2 have the same rank and are distinguishable by the rules in the paragraph below, or, if not that, > > - [..]

The first bullet point excludes our conversion (as it is an Lvalue Transformation). The second one requires a difference in ranks, which isn't present, as both conversions have Exact match rank; The "rules in the paragraph below", i.e. in [over.ics.rank]/4, don't cover array-to-pointer conversions either.
So believe it or not, none of both conversion sequences is better than the other, and thus the char const*-overload is picked.


Possible workaround: Define the second overload as a function template as well, then partial ordering kicks in and selects the first one.

template <typename T>
auto foo(T s)
  -> std::enable_if_t<std::is_convertible<T, char const*>{}>
{
    std::cout << "raw, size=" << std::strlen(s) << std::endl;
}

Demo.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBarryView Question on Stackoverflow
Solution 1 - C++ColumboView Answer on Stackoverflow