Почему неявное преобразование не является неоднозначным для не примитивных типов?
Дан простой шаблон класса с несколькими неявными функциями преобразования (неявный конструктор и оператор преобразования), как в следующем примере:
template<class T>
class Foo
{
private:
T m_value;
public:
Foo();
Foo(const T& value):
m_value(value)
{
}
operator T() const {
return m_value;
}
bool operator==(const Foo<T>& other) const {
return m_value == other.m_value;
}
};
struct Bar
{
bool m;
bool operator==(const Bar& other) const {
return false;
}
};
int main(int argc, char *argv[])
{
Foo<bool> a (true);
bool b = false;
if(a == b) {
// This is ambiguous
}
Foo<int> c (1);
int d = 2;
if(c == d) {
// This is ambiguous
}
Foo<Bar> e (Bar{true});
Bar f = {false};
if(e == f) {
// This is not ambiguous. Why?
}
}
Операторы сравнения, включающие примитивные типы (bool
, int
), как и ожидалось, неоднозначны - компилятор не знает, должен ли он использовать оператор преобразования для преобразования экземпляра левого класса шаблона в примитивный тип или использовать конструктор преобразования для преобразования тип примитива справа к ожидаемому экземпляру шаблона класса.
Однако последнее сравнение, включающее простую struct
, не является двусмысленным. Зачем? Какая функция преобразования будет использоваться?
Протестировано с компилятором msvc 15.9.7.
Ответы
Ответ 1
Согласно [over.binary]/1
Таким образом, для любого бинарного оператора @
[email protected]
может интерпретироваться как [email protected](y)
или [email protected](x,y)
.
Согласно этому правилу, в случае e == f
, компилятор может интерпретировать его только как e.operator==(f)
, а не как f.operator==(e)
. Так что нет никакой двусмысленности; operator==
вы определили как член Bar
, просто не подходит для разрешения перегрузки.
В случае a == b
и c == d
встроенный operator==(int, int)
-кандидат operator==(int, int)
(см. [Over.built]/13) конкурирует с operator==
определенным как член Foo<T>
.
Ответ 2
Перегрузки операторов, реализованные как функции-члены, не допускают неявного преобразования их левого операнда, который является объектом, для которого они вызываются.
Это всегда помогает выписать явный вызов перегрузки оператора, чтобы лучше понять, что именно он делает:
Foo<Bar> e (Bar{true});
Bar f = {false};
// Pretty explicit: call the member function Foo<Bar>::operator==
if(e.operator ==(f)) { /* ... */ }
Это нельзя спутать с оператором сравнения в Bar
, потому что это потребует неявного преобразования левой части, что невозможно.
Вы можете вызвать неоднозначность, аналогичную той, которую вы видите со встроенными типами, когда определяете Bar
и его оператор сравнения следующим образом:
struct Bar { bool m; };
// A free function allows conversion, this will be ambiguous:
bool operator==(const Bar&, const Bar&)
{
return false;
}
Это хорошо продемонстрировано и объяснено в статье "Эффективность Скотта Мейерса" C++, пункт 24.