#include <iostream>
using namespace std;
struct CL2
{
CL2(){}
CL2(const CL2&){}
};
CL2 cl2;
struct CL1
{
CL1(){}
operator CL2&(){cout<<"operator CL2&"; return cl2;}
operator const CL2&(){cout<<"operator const CL2&"; return cl2;}
};
CL1 cl1;
int main()
{
CL1 cl1;
CL2 cl2 (cl1);
}
И clang, и gcc предоставляют неоднозначный оператор преобразования, но Visual Studio компилирует нормально и печатает «оператор const CL2&». Как должно быть правильно в соответствии со стандартом?
Как я понимаю, преобразование CL1 в const CL2& находится в контексте инициализации копирования (как часть прямой инициализации объекта cl2). Я видел черновик n4296, [over.match.copy]:
Предполагая, что «cv1 T» является типом объекта, который инициализируется,
с типом класса T функции-кандидаты выбираются следующим образом:
— Конвертирующие конструкторы (12.3.1) из T являются кандидатами
функции.
— когда тип выражения инициализатора является
тип класса «cv S», неявные функции преобразования S и его
базовые классы считаются. При инициализации временной привязки
к первому параметру конструктора, где параметр имеет тип
«Ссылка на возможно c-квалифицированную T» и конструктор называется
с одним аргументом в контексте прямой инициализации
объект типа «cv2 T», явные функции преобразования также
считается. Те, которые не скрыты в S и дают тип, чей
cv-неквалифицированная версия того же типа, что и T, или является производным классом
из них являются кандидатами функции. Функции преобразования, которые возвращают
«Ссылка на X» возвращает lvalues или xvalues, в зависимости от типа
ссылка типа X и, следовательно, считается, что дают X для этого
процесс выбора кандидатских функций.
То есть оба оператора преобразования считаются возвращаемыми CL2 и const CL2 (а не просто CL2 без const), и остается решить, какое преобразование лучше: CL2 -> const CL2& или const CL2 -> const CL2&, Второй случай кажется более подходящим. Должно ли в этом контексте рассматриваться улучшение конверсии квалификации? Или оба случая являются преобразованием идентичности? Я не смог найти его в стандарте
Поскольку оба оператора преобразования имеют одинаковые подписи, единственным способом, которым один из них может быть предпочтительнее другого, является применение [over.match.best] / (1.4)…
— контекст является инициализацией путем пользовательского преобразования (см. 8.5,
13.3.1.5 и 13.3.1.6) и стандартную последовательность преобразования из возвращаемого типаF1
к типу назначения (то есть типу
инициализируемая сущность) является лучшей последовательностью преобразования, чем
стандартная последовательность преобразования из возвращаемого типаF2
к
тип назначения.
… Или (1.5):
— контекст является инициализацией функцией преобразования для прямого
привязка ссылки (13.3.1.6) ссылки на тип функции, […]
Понятно, что ни то, ни другое не применимо, отсюда и двусмысленность. Возможный способ устранения неоднозначности:
operator CL2&();
operator const CL2&() const;
демонстрация; Здесь исходная стандартная последовательность преобразования предыдущей перегрузки для неявного аргумента объекта лучше согласно [over.ics.rank] / (3.2.6), что решает [over.match.best] / (1.3).
Других решений пока нет …