Как компилятор узнает, использовать ли перегрузку оператора-члена или глобальные перегрузки оператора?

У меня есть вопрос об операторах 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

Я был бы соблазн думать, что все в этом законно и что глобальный оператор имеет приоритет над членом-участником, но единственное, что я мог найти об этом в Интернете, казалось, предположить, что эти дополнения были неоднозначными вызовами, так что это действительно так или я просто смотрю на неопределенное поведение здесь?

2

Решение

Функции-члены и не-члены участвуют в разрешении перегрузки на равных правах. Чтобы сделать их сопоставимыми, каждая функция-член расширяется компилятором с неявным параметром объекта.
([Over.match.funcs] / р2):

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

[Over.match.funcs] / р5:

Во время разрешения перегрузки подразумеваемый объектный аргумент неотличим от других аргументов. Однако неявный параметр объекта сохраняет свою идентичность, так как никакие пользовательские преобразования не могут быть применены для достижения соответствия типа с ним.

Учитывая, что неявный параметр объекта также наследует ref- и cv-квалификации от нестатической функции-члена, это в основном означает, что с точки зрения компилятора оператор-член объявлен как:

MyClass MyClass::operator+(int) const;

в некоторой степени эквивалентно:

MyClass operator+(const MyClass&, int);

Единственное исключение, которое отличает его от обычной функции, не являющейся членом, состоит в том, что для первого не учитываются определенные пользователем преобразования (неявный объект) параметр (вот почему 1 + a никогда не будет конвертировать 1 в A используя какой-то конвертирующий конструктор A(int) для того, чтобы позвонить A::operator+(const A&)), и это временный экземпляр Можно быть связанным неконстантной ссылкой, сгенерированной для неконстантной квалифицированной функции-члена.

Член и глобальный operator+ неоднозначны в вашем коде и должны выдавать ошибку как таковую.

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]