michael3.14
michael3.14

Reputation: 333

Overload resolution of a pointer and a container with pointers

I wrote a function with two overloads - one for a single pointer and one for a vector of pointers. At some point in the code I wanted to pass an empty vector to the function and used {} as the argument.

#include <vector>
#include <iostream>

void f(std::vector<const int*> v)
{
    std::cout << "vector overload" << std::endl;
}
 
void f(const int* i)
{
    std::cout << "pointer overload" << std::endl;
}

int main()
{
    f({});
    return 0;
}

Unfortunately, this will not call the vector overload with an empty vector but the pointer overload with a nullptr.

The solution to the problem was to replace the pointer overload with a reference, which can't be initialised with {}.

void f(const int& i);

Why doesn't the compiler complain about an ambiguous call? Why does it prefer the pointer overload?

Upvotes: 14

Views: 1444

Answers (3)

wohlstad
wohlstad

Reputation: 28609

The compiler prefers to choose an overload that requires less conversions (preferably none).

In this case a pointer can be directly initialized from {} in order to invoke the second overload.
You can use e.g.:

const int* p{};  // will initialize `p` to `nullptr`

On the other hand creating a std::vector<const int*> from {} in order to invoke the first overload requires a user-defined conversion.

Therefore the compiler prefers to call the second overload.

Upvotes: 8

Brian Sterling
Brian Sterling

Reputation: 39

Let us first consider what happens when function overloading is specified. The compiler will search for an ambigous call during compilation so we straight off the bat can eliminate run time errors and strictly adhere is dry semantics at this point. The compiler will only complain about an ambigous call when performing parameter matching in overloaded functions. During function overloading the compiler will generate candidate functions to form a candidate set. The candidate set is generated following function loading semantics, in which the function parameters are sequentially checked and the infered/implied function parameters also follow likewise analysis. At this point if the function parameter types are cast to another type or decay (a cast in which the parameter type decays in value) to another type according to parameter selection semantics. The overlap in the stated overload functions in the candidate set will not cause the compiler to throw an ambiguous call warning. The function overload selection will defer to the overload which requires the least overhead in execution. In essence the pointer overload is prefered as it ranks higher in the priority ranking list, (the pointer type is not a user defined type). I've tried to go into some amout on details about the semantics the compiler goes threough when it ranks priority. Hope this hhelps.

Upvotes: 1

songyuanyao
songyuanyao

Reputation: 172964

Because the conversion from braced-list to std::vector is classified as user-defined conversion sequence, which has lower rank.

You can add another overload taking std::initializer_list which will be preferred. E.g.

void f(std::initializer_list<const int*> v)
{
    f(std::vector<const int*>(v)); // forward to the overload taking std::vector
}

LIVE

Upvotes: 18

Related Questions