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

Time:07-16

tl;dr; why is - regarding overload resolution / function declaration - the pointer type treated as a more 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 not int* (pointer type).

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

I am not asking for a reference to but more for an explanation (if related at all) of:

9.3.4.6 Functions[dcl.fct]

1 In a declaration T D [...]

5 The type of a function is determined using the following rules. The type of each parameter (including function parameter packs) is determined from its own parameter-declaration ([dcl.decl]). After determining the type of each parameter, any parameter of type “array of T” or of function type T is adjusted to be “pointer to T”. After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type. The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function's parameter-type-list.

[Note 3: This transformation does not affect the types of the parameters. For example, int(*)(const int p, decltype(p)*) and int(*)(int, const int*) are identical types. — end note]

[Example 2:

void f(char*);                  // #1
void f(char[]) {}               // defines #1
void f(const char*) {}          // OK, another overload
void f(char *const) {}          // error: redefines #1

void g(char(*)[2]);             // #2
void g(char[3][2]) {}           // defines #2
void g(char[3][3]) {}           // OK, another overload

void h(int x(const int));       // #3
void h(int (*)(int)) {}         // defines #3

end example]

Unfortunately, the example lacks the explicit case like

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

Why is

any parameter of type “array of T” [...] adjusted to be “pointer to T”

Can someone explain some reasoning behind this decision?

Kind regards

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.

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