56

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?

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    Related: http://stackoverflow.com/questions/5347444/overload-resolution-and-arrays-which-function-should-be-called – David G Jan 30 '15 at 19:50
  • 4
    `foo<>("hello");` will call the template, if it helps. I think that's a simple way to reject non-template. And to force the non-template use `(&foo)("hello")` - you can take the address of a non-template, but not of a template. – Aaron McDaid Jan 31 '15 at 09:18
  • Possible duplicate of [array decay to pointer and overload resolution](https://stackoverflow.com/questions/21972652/array-decay-to-pointer-and-overload-resolution) – Language Lawyer Feb 04 '19 at 03:24
  • @LanguageLawyer No? The _why_ part of this question is pretty salient - the linked question does not ask why and the answer does not answer why. This answer does. They're related, but they're not duplicates. – Barry Feb 04 '19 at 14:21

1 Answers1

44

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.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • Imagine a function `bar` that takes an object of type `X`. Also, `X` has only one constructor that takes a `const char *` as argument. You can call `bar("hello");` even though this looks like you're getting two conversions, array-to-pointer and pointer-to-X, when normally just one implicit conversion is allowed. I guess we get array-to-pointer decay "for free". Is that an acceptable way to put it? – Aaron McDaid Jan 30 '15 at 20:35
  • 2
    @AaronMcDaid Nope. The array to pointer conversion is a standard-conversion sequence, while the constructor is a user-defined conversion sequence. The latter is only allowed once, but the former can also occur in combination with it. :) – Columbo Jan 30 '15 at 20:49
  • *the conversion constructor that is invoked is part of a user-defined conversion sequence. A user-defined conversion sequence consists of a user-defined conversion (ctor call) plus an initial standard coversion sequence (array-to-pointer) plus a final standard conversion sequence (here, identity). It is of course not the sequence itself. – Columbo Jan 30 '15 at 21:09