У меня есть вопрос об операторах c ++, который, я надеюсь, найдет здесь ответ.
Краткая версия вопроса есть в названии, но если есть какие-либо сомнения относительно того, что я действительно спрашиваю, вот длинная версия.
Операторы c ++ могут быть перегружены, так что становится возможным писать такие вещи:
MyClass a(1), b(2);
Myclass c = a + b;
Из того, что я понимаю, типичный способ реализации, который будет выглядеть так:
class MyClass
{
private:
int val;
public:
explicit MyClass(int _val);
MyClass operator+(MyClass const& other) const;
MyClass operator+(int i) const;
};
Который в этом случае также включает перегрузку с типом int
, что позволяет писать такие вещи:
MyClass a(1);
Myclass b = a + 2;
Но не так:
MyClass a(1);
Myclass b = 2 + a;
потому что это было бы как звонить 2.operator+(a)
, а также 2
не объект Поскольку программисты хотели бы перегружать операторы таким образом, чтобы это было возможно, существует второй способ их реализации, который будет выглядеть следующим образом:
class MyClass
{
private:
int val;
public:
explicit MyClass(int _val);
friend MyClass operator+(MyClass const& lhs, MyClass const& rhs);
friend MyClass operator+(int lhs, MyClass const& rhs);
friend MyClass operator+(MyClass const& lhs, int rhs);
};
что позволяет все три типа дополнений.
Теперь, что беспокоит меня: что, если мы реализуем оба одновременно? Как компилятор решает, использовать ли оператор члена или глобальный?
Если честно, то, какой оператор вызывается, не имеет значения в любой разумной реализации, и они не должны ни возвращать разные вещи, ни иметь разные побочные эффекты, но я попытался реализовать это, чтобы увидеть, что происходит:
class MyClass
{
private:
int val;
public:
explicit MyClass(int _val) : val(_val){}
MyClass operator+(MyClass const& other) const
{
cout << "Call to member operator+ for MyClass+MyClass" << endl;
return MyClass(val + other.val);
}
MyClass operator+(int other) const
{
cout << "Call to member operator+ for MyClass+int" << endl;
return MyClass(val + other);
}
friend MyClass operator+(MyClass const& lhs, MyClass const& rhs)
{
cout << "Call to global operator+ for MyClass+MyClass " << endl;
return MyClass(lhs.val + rhs.val);
}
friend MyClass operator+(int lhs, MyClass const& rhs)
{
cout << "Call to global operator+ for int+MyClass " << endl;
return MyClass(lhs + rhs.val);
}
friend MyClass operator+(MyClass const& lhs, int rhs)
{
cout << "Call to global operator+ for MyClass+int " << endl;
return MyClass(lhs.val + rhs);
}
};int main() {
MyClass a(1), b(2);
int i(3);
MyClass r_0 = a.operator+(b);
MyClass r_1 = a.operator+(i);
MyClass r_2 = operator+(a,b);
MyClass r_3 = operator+(a,i);
MyClass r_4 = operator+(i,a);
MyClass r_5 = a + b;
MyClass r_6 = a + i;
MyClass r_7 = i + a;
return 0;
}
который компилирует и печатает
Call to member operator+ for MyClass+MyClass
Call to member operator+ for MyClass+int
Call to global operator+ for MyClass+MyClass
Call to global operator+ for MyClass+int
Call to global operator+ for int+MyClass
Call to global operator+ for MyClass+MyClass
Call to global operator+ for MyClass+int
Call to global operator+ for int+MyClass
Я был бы соблазн думать, что все в этом законно и что глобальный оператор имеет приоритет над членом-участником, но единственное, что я мог найти об этом в Интернете, казалось, предположить, что эти дополнения были неоднозначными вызовами, так что это действительно так или я просто смотрю на неопределенное поведение здесь?
Функции-члены и не-члены участвуют в разрешении перегрузки на равных правах. Чтобы сделать их сопоставимыми, каждая функция-член расширяется компилятором с неявным параметром объекта.
([Over.match.funcs] / р2):
Набор функций-кандидатов может содержать как функции-члены, так и функции, не являющиеся членами, которые должны быть разрешены для одного и того же списка аргументов. Чтобы списки аргументов и параметров были сопоставимы в этом гетерогенном наборе, считается, что функция-член имеет дополнительный первый параметр, называемый параметром неявного объекта, который представляет объект, для которого была вызвана функция-член. В целях разрешения перегрузки как статические, так и нестатические функции-члены имеют неявный параметр объекта, а конструкторы — нет.
Во время разрешения перегрузки подразумеваемый объектный аргумент неотличим от других аргументов. Однако неявный параметр объекта сохраняет свою идентичность, так как никакие пользовательские преобразования не могут быть применены для достижения соответствия типа с ним.
Учитывая, что неявный параметр объекта также наследует ref- и cv-квалификации от нестатической функции-члена, это в основном означает, что с точки зрения компилятора оператор-член объявлен как:
MyClass MyClass::operator+(int) const;
в некоторой степени эквивалентно:
MyClass operator+(const MyClass&, int);
Единственное исключение, которое отличает его от обычной функции, не являющейся членом, состоит в том, что для первого не учитываются определенные пользователем преобразования (неявный объект) параметр (вот почему 1 + a
никогда не будет конвертировать 1
в A
используя какой-то конвертирующий конструктор A(int)
для того, чтобы позвонить A::operator+(const A&)
), и это временный экземпляр Можно быть связанным неконстантной ссылкой, сгенерированной для неконстантной квалифицированной функции-члена.
Член и глобальный operator+
неоднозначны в вашем коде и должны выдавать ошибку как таковую.
Других решений пока нет …