Ошибка: нет соответствующей функции-члена для вызова push_back - PullRequest
0 голосов
/ 14 мая 2018

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

using namespace std;

struct mystruct {
  int id;
  vector<int> y;
  mystruct(const int id):id(id) {}
  bool operator<(const mystruct& x) const { return id < x.id; }
  bool operator==(const mystruct& x) const { return id == x.id; }
};

void test() {
  std::set<mystruct> sx;
  mystruct x(1);
  x.y.push_back(1); x.y.push_back(2);
  sx.insert(x);
  //
  set<mystruct>::iterator i = sx.find(1);
  const mystruct* x1 = &(*i);
  const mystruct x2 = *x1;
  cout << &(i->y) << endl;
  cout << &(x1->y) << endl;
  cout << x2.id << endl;
  x2.y.push_back(3);
  i->y.push_back(4);
}

Похоже, что итератор возвращает постоянный объект и не позволяет мне использовать push_back() для изменения вектора y.Как я могу преодолеть это?

Ошибка:

test.cpp:27:8: error: no matching member function for call to 'push_back'
  x2.y.push_back(z);
  ~~~~~^~~~~~~~~
/opt/local/libexec/llvm-6.0/include/c++/v1/vector:688:36: note: candidate function not viable: 'this' argument has type 'const vector<int>', but method is not marked const
    _LIBCPP_INLINE_VISIBILITY void push_back(const_reference __x);
                                   ^
/opt/local/libexec/llvm-6.0/include/c++/v1/vector:691:36: note: candidate function not viable: 'this' argument has type 'const vector<int>', but method is not marked const
    _LIBCPP_INLINE_VISIBILITY void push_back(value_type&& __x);
                                   ^

Ответы [ 2 ]

0 голосов
/ 14 мая 2018

Причина, по которой вы не можете изменить x2, заключается в том, что она объявлена ​​const, как было указано @dasblinkenlight. Комментарий @ songyuanyao является правильным для доступа к объекту, на который ссылается итератор, но не вполне отвечает на вопрос, потому что он не говорит , почему set итераторы разрешают только const доступ.

Причина этого в том, что, как вы знаете, std::set - это упорядоченный контейнер, структура которого определяется путем сравнения записей друг с другом с использованием (по умолчанию) operator <. Это означает, что существует контейнер , инвариант , такой, что если элемент a предшествует другому элементу b в std::set, то следует !(b < a). Я сказал так, потому что это также верно для std::multiset. Поскольку в наборе дубликаты недопустимы, из этого следует, что на самом деле, если a предшествует b, то a < b. Если этот инвариант был нарушен, то любая операция набора, которая требует сортировки набора, например find или insert, будет иметь неожиданное (точнее, неопределенное) поведение.

Следовательно, std::set не позволит вам изменить элемент, используя iterator, потому что вы можете невольно изменить элементы элемента таким образом, чтобы это влияло на его правильное место в наборе, нарушая инвариант и, таким образом, вызывая неопределенное поведение.

К сожалению, компилятор недостаточно умен, чтобы понимать, что ваша функция сравнения оценивает только определенные элементы, в данном случае только id. Если бы компилятор и язык были способны анализировать и выражать это, они могли бы подумать, что хотя i->id должно быть ссылкой на const, i->m для любого другого члена m может безопасно быть ссылкой на не const .

Существует как минимум четыре возможных решения вашей проблемы:

  1. Самое простое, но самое уродливое решение - пометить участников, которых нужно изменить, как mutable. Обратите внимание, что вы сами должны убедиться, что их изменение не влияет на порядок сортировки.
  2. Другим довольно уродливым решением является преднамеренное использование const_cast, как в const_cast<mystruct &>(*i), где i - итератор. Опять же, никогда не меняйте элемент, который влияет на порядок сортировки таким образом.
  3. Более элегантное решение, которое, однако, имеет дополнительные накладные расходы времени выполнения, заключается в добавлении уровня косвенности указателя (например, с использованием std::unique_ptr) к свойствам, которые вы хотите изменить. Учтите, однако, что если бы вы использовали pointee в своей функции сравнения, вы все равно рискуете нарушить установленный инвариант, только теперь компилятор и библиотека больше не будут препятствовать вам в этом!
  4. Единственный способ, который работает даже , если вы хотите изменить элементы, влияющие на порядок сортировки, - это сделать копию элемента, изменить копию, стереть старый элемент, а затем повторно вставить копию , Это позволяет контейнеру вставить копию в другое положение, если необходимо.

Заключительные ноты:

  • Хеш-контейнер, такой как std::unordered_set, будет иметь точно такую ​​же проблему, только в этом случае это не функция сравнения, а функции хеширования и равенства, которые вы должны учитывать.
  • По указанным причинам std::map или std::unordered_map могут лучше подходить для вашей проблемной области, поскольку в этом случае библиотека знает, что mapped_type не используется для определения структуры контейнера. Также обратите внимание, что value_type для std::map или std::unordered_map равно std::pair<const key_type, mapped_type> вместо std::pair<key_type, mapped_type> точно по той же причине, по которой изменение ключа может нарушить инварианты контейнера.
0 голосов
/ 14 мая 2018

Поскольку x2 объявляется с квалификатором const, то есть const mystruct x2, компилятор C ++ рассматривает только const -квалифицированные функции-члены для всех вызовов на x2 и любых его членов. В частности, он ищет void push_back (const int& val) const функцию-член для вызова. Очевидно, что такой функции нет, потому что push_back должен изменить контейнер, поэтому компилятор выдает ошибку, объясняющую, что именно происходит:

функция-кандидат недопустима: 'this' аргумент имеет тип 'const vector<int>', но метод не помечен const

Единственный способ исправить это в вашем коде - удалить квалификатор const из объявления x2.

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