литерал '0', являющийся допустимым кандидатом для int и const string & overloads, вызывает неоднозначный вызов

Я недавно исправил ошибку.

В следующем коде одна из перегруженных функций была константной, а другая - нет. Проблема будет исправлена, если сделать обе функции константными.

У меня вопрос, почему компилятор жаловался на это только тогда, когда параметр был 0.

#include <iostream>
#include <string>

class CppSyntaxA
{
public:
    void f(int i = 0) const { i++; }
    void f(const std::string&){}
};

int main()
{
    CppSyntaxA a;
    a.f(1);  // OK
    //a.f(0);  //error C2666: 'CppSyntaxA::f': 2 overloads have similar conversions
    return 0;
}

Ответ 1

0 является особенным в C++. Нулевой указатель имеет значение 0 поэтому C++ разрешит преобразование 0 в тип указателя. Это значит, когда вы звоните

a.f(0);

Вы можете вызывать void f(int я = 0) const с int со значением 0, или вы можете вызывать void f(const std::string&) с void f(const std::string&) char* инициализированным нулевым значением.

Обычно версия int будет лучше, так как это точное совпадение, но в этом случае версия int является const, поэтому она требует "преобразования" a в const CppSyntaxA, где версия std::string не требует такого преобразования, но делает требуется преобразование в char* а затем в std::string. Это считается достаточным изменением в обоих случаях, чтобы считаться равным преобразованием и, следовательно, неоднозначным. Если обе функции будут const или const это решит проблему, и будет выбрана перегрузка int, поскольку она лучше.

Ответ 2

У меня вопрос, почему компилятор жаловался на это только тогда, когда параметр был 0.

Потому что 0 - это не только целочисленный литерал, но и литерал нулевого указателя. 1 не является литералом нулевого указателя, поэтому нет никакой двусмысленности.

Неоднозначность возникает из неявного конструктора преобразования std::string который принимает указатель на символ в качестве аргумента.

Теперь преобразование идентичности из int в int в противном случае было бы предпочтительнее преобразования из указателя в строку, но есть другой аргумент, который включает преобразование: неявный объектный аргумент. В одном случае это преобразование из CppSyntaxA& в CppSyntaxA& то время как в другом случае это CppSyntaxA& для const CppSyntaxA&.

Таким образом, одна перегрузка предпочтительна из-за одного аргумента, а другая перегрузка предпочтительна из-за другого аргумента, и, таким образом, не существует однозначно предпочтительной перегрузки.

Проблема будет исправлена, если сделать обе функции константными.

Если обе перегрузки квалифицированы как const, то последовательность преобразования неявных аргументов объекта идентична, и поэтому одно из перегрузок однозначно предпочтительнее.