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

У меня есть вопрос об операторах 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 isnне объект.Поскольку программисты хотели бы перегружать операторы таким образом, чтобы это было возможно, существует второй способ их реализации, который будет выглядеть следующим образом:

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

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

1 Ответ

0 голосов
/ 18 октября 2018

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

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

[over.match.funcs] / p5 :

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

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

MyClass MyClass::operator+(int) const;

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

MyClass operator+(const MyClass&, int);

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

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

...