Home > Mobile >  Overload resolution and array-to-pointer decay - why is int (&a)[2] and int* a considered equally ex
Overload resolution and array-to-pointer decay - why is int (&a)[2] and int* a considered equally ex

Time:07-19

tl;dr; why is - regarding overload resolution / function declaration - the pointer type treated as a more equally exact match than the array type, even though a passed variable is really of type array type and not "just" of pointer type


I already looked at some of the equivalent questions (e.g. 21972652, 29602638 and 72929287) regarding overload resolution and array-to-pointer decay and learned that the compiler will treat void foo(int (&a)[3]); and void foo(int* a); for the same when passing in int bar[3];, but I do not really understand why it does so (despite the fact that it is stated to do so in the specification).

Consider the following example, which does not compile due to ambiguous function call (online):

#include <iostream>
#include <utility>

void foo(int (&a)[2])
{
    std::cout << "Called concrete foo with N = 2";
}

void foo(int* a)
{
    std::cout << "Called pointer foo" << std::endl;
}

int main()
{
    int a1[] = { 0x00 };
    int a2[] = { 0x00, 0x01 };
    int a3[] = { 0x00, 0x01, 0x02 };

    foo(a1);
    foo(a2);
    foo(a3);
}

My naive understanding tells me, that for the function call foo(a2), int (&a)[2] should be more concrete / exact regarding overload resolution than int* a, because the actual type of the variable a2 is int[2] (array type) and not int* (pointer type).

So why does the compiler not favor void foo(int (&a)[2]) over void foo(int* a)?

Kind regards


this first part of the question (why does the compiler not favor void foo(int (&a)[2]) over void foo(int* a)) is already answered and marked as accepted, since this was my initial question, nevertheless it would be nice if someone could explain some reasoning behind this decision, i.e. why it is the way it is and whether there is any counter example (besides backward compatibility) to favor void foo(int (&a)[2]) over void foo(int* a).

CodePudding user response:

@463035818_is_not_a_number already explained in their answer why void foo(int* a); is a better match than template<std::size_t N> void foo(int (&a)[N]).

This answer only applies to the second example in the question:

void foo(int (&a)[2]);
void foo(int* a);

The reason why the call is ambigous is because the conversion sequence for both has the same rank.
As per 12.4.3 Best viable function [over.match.best] (2):

(2) 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
[...]
(2.1) for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), [...]

There are three possible ranks for conversions: Exact Match, Promotion or Conversion, as per 12.4.3.1.1 Standard conversion sequences [over.ics.scs] (3):

(3) Each conversion [...] also has an associated rank (Exact Match, Promotion, or Conversion). These are used to rank standard conversion sequences. The rank of a conversion sequence is determined by considering the rank of each conversion in the sequence and the rank of any reference binding. If any of those has Conversion rank, the sequence has Conversion rank; otherwise, if any of those has Promotion rank, the sequence has Promotion rank; otherwise, the sequence has Exact Match rank.

Conversion Category Rank
No conversions required Identity Exact Match
Lvalue-to-rvalue conversion Lvalue Transformation Exact Match
Array-to-pointer conversion Lvalue Transformation Exact Match
Function-to-pointer conversion Lvalue Transformation Exact Match
Qualification conversions Qualification Adjustment Exact Match
Function pointer conversion Qualification Adjustment Exact Match
Integral promotions Promotion Promotion
Floating-point promotion Promotion Promotion
Integral conversions Conversion Conversion
Floating-point conversions Conversion Conversion
Floating-integral conversions Conversion Conversion
Pointer conversions Conversion Conversion
Pointer-to-member conversions Conversion Conversion
Boolean conversions Conversion Conversion

The ordering of which is as follows:

12.4.3.2 Ranking implicit conversion sequences [over.ics.rank] (4)

(4) Standard conversion sequences are ordered by their ranks: an Exact Match is a better conversion than a Promotion, which is a better conversion than a Conversion. Two conversion sequences with the same rank are indistinguishable unless one of the following rules applies: [...]

(there are also a bunch of exceptions listed in that paragraph, but none of them apply to the given example)


void foo(int (&a)[2]) requires a conversion sequence consisting of a single Identity conversion, as per 12.4.3.1.4 Reference binding [over.ics.ref] (1):

(1) When a parameter of reference type binds directly to an argument expression, the implicit conversion sequence is the identity conversion [...]

void foo(int* a); requires a conversion sequence consisting of a single Array-to-pointer conversion conversion, as per 7.3.2 Array-to-pointer conversion [conv.array] (1):

(1) An lvalue or rvalue of type “array of N T” or “array of unknown bound of T” can be converted to a prvalue of type “pointer to T”. [...]

So both conversion sequences have the "Exact Match" rank - and there is no special exception for this case in 12.4.3.2 Ranking implicit conversion sequences [over.ics.rank] (3) and (4) - therefore the conversion sequences are indistinguishable and the function call ambigous (from the viewpoint of a standard-compliant compiler).


About the why it is that way:

Unfortunately i couldn't find any normative references regarding references to c-style arrays in the context of overload resolution.

There are also no backwards-compatibility constraints to consider; there is no overloading in C .

Most likely the only ones that could answer that question definitely are the authors of the C -language themselves or the members of the C committee.

My guess would be that it is mostly a non-issue in real code, given that passing a pointer to an array without an additional size-parameter is not very practical (the called function doesn't know how many elements there are nor if it is even safe to dereference the passed-in pointer)

So in practice you commonly see 2 sets of overloads:

// array overloads
template<std::size_t N>
void foobar(int (&arr)[N]);
template<std::size_t N>
void foobar(int (&&arr)[N]);

// pointer   size
void foobar(int* ptr, std::size_t length);

which completely avoids this issue, due to the second parameter.

Using std::array is also an option, of course, and also avoids most of the common pitfalls of c-style arrays. (What's wrong with arrays?)

Additionally the ambiguity can be easily resolved by making void foo(int*) a template, e.g.:

void foo(int (&)[2]) {}

template<class = void>
void foo(int*) {}

(which is the inverse of the version you originally had in your question - non-templated functions are preferred, so making the second one a template makes void foo(int (&)[2]) a better match)

CodePudding user response:

So why does the compiler not favor void foo(int (&a)[2]) over void foo(int* a)?

Because the array to pointer conversion is not considered worse than the identity conversion. This can be seen from ics.rank-3.2.1 which states:

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 [over.ics.scs], excluding any Lvalue Transformation; the identity conversion sequence is considered to be a subsequence of any non-identity conversion sequence) or, if not that,

(emphasis mine)

The important thing to note here is that array to pointer conversion is lvalue transformation.

  • Related