Свободный оператор -> * перегружает зло? - PullRequest
38 голосов
/ 23 апреля 2010

Я просматривал раздел 13.5 после , опровергая идею , что встроенные операторы не участвуют в разрешении перегрузки, и заметил, что в operator->* нет раздела.Это просто общий двоичный оператор.

Его братья, operator->, operator* и operator[], все должны быть нестатическими функциями-членами.Это исключает определение перегрузки свободной функции для оператора , обычно , используемого для получения ссылки из объекта.Но необычное operator->* не учитывается.

В частности, operator[] имеет много общего.Это двоичный файл (они упустили прекрасную возможность сделать его n-арным), и он принимает какой-то контейнер слева и какой-то локатор справа.Его секция специальных правил, 13.5.5, похоже, не имеет никакого реального эффекта, кроме как запрещение свободных функций.(И это ограничение даже исключает поддержку коммутативности!)

Так, например, это совершенно законно :

#include <utility>
#include <iostream>
using namespace std;

template< class T >
T &
operator->*( pair<T,T> &l, bool r )
    { return r? l.second : l.first; }

template< class T >
 T & operator->*( bool l, pair<T,T> &r ) { return r->*l; }

int main() {
        pair<int, int> y( 5, 6 );
        y->*(0) = 7;
        y->*0->*y = 8; // evaluates to 7->*y = y.second
        cerr << y.first << " " << y.second << endl;
}

Легко найти применение, но альтернативуСинтаксис не так уж и плох.Например, масштабированные индексы для vector:

v->*matrix_width[2][5] = x; // ->* not hopelessly out of place

my_indexer<2> m( v, dim ); // my_indexer being the type of (v->*width)
m[2][5] = x; // it is probably more practical to slice just once

Комитет по стандартизации забыл предотвратить это, считалось ли это слишком уродливым, чтобы беспокоиться, или есть реальные случаи использования?

Ответы [ 4 ]

20 голосов
/ 18 декабря 2013

Лучший пример, который мне известен, это Boost.Phoenix , который перегружает этот оператор для реализации ленивого доступа к элементу.

Для тех, кто незнаком с Phoenix, это очень изящная библиотекасоздание акторов (или функциональных объектов), которые выглядят как нормальные выражения:

( arg1 % 2 == 1 )     // this expression evaluates to an actor
                 (3); // returns true since 3 % 2 == 1

// these actors can also be passed to standard algorithms:
std::find_if(c.begin(), c.end(), arg1 % 2 == 1);
// returns iterator to the first odd element of c

Это достигается за счет перегрузки operator% и operator==.- применительно к субъекту arg1 эти операторы возвращают другого субъекта.Диапазон выражений, которые можно построить таким образом, чрезвычайно велик:

// print each element in c, noting its value relative to 5:
std::for_each(c.begin(), c.end(),
  if_(arg1 > 5)
  [
    cout << arg1 << " > 5\n"
  ]
  .else_
  [
    if_(arg1 == 5)
    [
      cout << arg1 << " == 5\n"
    ]
    .else_
    [
      cout << arg1 << " < 5\n"
    ]
  ]
);

После того, как вы некоторое время используете Phoenix (не то, чтобы вы когда-либо возвращались), вы попробуете что-то вроде этого:

typedef std::vector<MyObj> container;
container c;
//...
container::iterator inv = std::find_if(c.begin(), c.end(), arg1.ValidStateBit);
std::cout << "A MyObj was invalid: " << inv->Id() << std::endl;

Что не удастся, потому что, конечно, у актеров Феникса нет члена ValidStateBit.Феникс обходит это, перегрузив operator->*:

(arg1 ->* &MyObj::ValidStateBit)              // evaluates to an actor
                                (validMyObj); // returns true 

// used in your algorithm:
container::iterator inv = std::find_if(c.begin(), c.end(), 
      (arg1 ->* &MyObj::ValidStateBit)    );

operator->*. Аргументы:

  • LHS: актер, возвращающий MyObj *
  • RHS: адрес члена

Возвращает субъект, который оценивает LHS и ищет в нем указанного члена.(NB: Вы действительно, действительно хотите убедиться, что arg1 вернет MyObj * - вы не видели значительную ошибку шаблона, пока не ошиблись в Phoenix. Эта маленькая программа сгенерировала 76 738 символов боли(Boost 1.54, gcc 4.6):

#include <boost/phoenix.hpp>
using boost::phoenix::placeholders::arg1;

struct C { int m; };
struct D { int n; };

int main() {
  ( arg1  ->*  &D::n ) (new C);
  return 0;
}
7 голосов
/ 25 апреля 2010

Я согласен с вами, что в стандарте есть несогласованность, он не допускает перегрузки operator[] функциями, не являющимися членами, и допускает его для operator->*.На мой взгляд, operator[] относится к массивам, а operator->* - к структурам / классам (получателю).Члены массива выбираются с использованием индекса.Члены структуры выбираются с помощью указателей членов.

Хуже всего то, что у нас может возникнуть искушение использовать ->* вместо operator[] для получения массива, подобного элементу

int& operator->*(Array& lhs, int i);

Array a;

a ->* 2 = 10;

Тамтакже еще одно возможное несогласованность.Мы можем использовать функцию, не являющуюся членом, для перегрузки operator+= и всех операторов вида @=), и мы не можем сделать это для operator=.

Я действительно не знаю, каково обоснованиесделайте следующее законным

struct X {
    int val;
    explicit X(int i) : val(i) {}
};
struct Z {
    int val;
    explicit Z(int i) : val(i) {}
};
Z& operator+=(Z& lhs, const X& rhs) {
    lhs.val+=rhs.val;
    return lhs;
}

Z z(2);
X x(3);
z += x;

и запретите

Z& operator=(Z& lhs, const X& rhs) {
    lhs.val=i;
    return lhs;
}

z = x;

Извините, что не отвечаете на ваш вопрос, но добавляете еще больше путаницы.

2 голосов
/ 02 мая 2010

Немного погуглив, я обнаружил больше случаев, когда люди спрашивали, используется ли когда-либо operator->*, чем реальные предложения.

Пара мест предлагает T &A::operator->*( T B::* ).Не уверен, отражает ли это намерение дизайнера или неверное представление о том, что T &A::operator->*( T A::* ) является встроенным.На самом деле не имеет отношения к моему вопросу, но дает представление о глубине, которую я нашел в онлайн-обсуждениях и литературе.

Было упоминание о «D & E 11.5.4», который, как я полагаю, называется «Разработка и развитие C ++».Возможно, это содержит подсказку.В противном случае, я просто сделаю вывод, что это стандартизация, и большинство остальных тоже упустили из виду бесполезное уродство.

Редактировать Смотрите ниже вставку цитаты D & E.

Чтобы выразить это количественно, ->* - самый трудный оператор связывания, который может быть перегружен свободной функцией.Все перегрузки выражений postfix и унарных операторов требуют нестатических сигнатур функций-членов.Следующим приоритетом после унарных операторов являются приведения в стиле C, которые, как можно сказать, соответствуют функциям преобразования (operator type()), которые также не могут быть свободными функциями.Затем наступает ->*, затем умножение.->* могло бы быть как [] или как %, они могли пойти в любом направлении, и они выбрали путь EEEEEEVIL .

1 голос
/ 23 апреля 2010

Стандарт (рабочий проект 2010-02-16, § 5.5) гласит:

Результатом выражения -> * является lvalue, только если его второй операнд указатель на элемент данных. Если второе операнд является нулевым указателем на член значение (4.11), поведение не определено.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...