Search code examples
c++language-lawyeroverloadinginitializer-listoverload-resolution

Overload resolution of int vs std::vector<int> with an initializer list of a single int


Why does c++ choose a primitive type overload match over a "better" matching initializer list?


#include <vector>

void foo([[maybe_unused]] int i) {}

void foo([[maybe_unused]] const std::vector<int>& v) {}

int main() {
    foo(0);
    foo({1,2,3});
    foo({0}); // calls foo(int) and issues a warning,
              // rather than what seems like the "better"
              // match foo(vector).. why? 
}
<source>:10:9: warning: braces around scalar initializer [-Wbraced-scalar-init]
    foo({0}); // calls foo(int) and issues a warning,
        ^~~

Perhaps "surprising" result, since the compiler chooses the option for which it then issues a diagnostic?

Using Clang 14

https://godbolt.org/z/1dscc5hM4


Solution

  • {0} doesn't have a type, so we need to try and convert it to the parameter types of the overload set. When considering

    void foo([[maybe_unused]] const std::vector<int>& v) {}
    

    We need to consult [over.ics.list]/7.2 which states

    Otherwise, the implicit conversion sequence is a user-defined conversion sequence whose second standard conversion sequence is an identity conversion.

    so we have a user defined conversion for this conversion sequence.

    Looking at

    void foo([[maybe_unused]] int i) {}
    

    We find the conversion covered in [over.ics.list]/10.1 which states

    if the initializer list has one element that is not itself an initializer list, the implicit conversion sequence is the one required to convert the element to the parameter type;

    The element in this case is 0, which is an integer literal which is an exact match standard conversion

    So now we have a user defined conversion vs a standard conversion and that is covered by [over.ics.rank]/2.1

    a standard conversion sequence is a better conversion sequence than a user-defined conversion sequence or an ellipsis conversion sequence, and

    and now we know that the standard conversion is a better conversion and that is why the int overload is chosen over the std::vector<int> overload.