Why does pointer decay take priority over a deduced template?

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.

Leave a Comment