std::erase и std::remove комбинация за изтриване на конкретен елемент не работи за конкретен пример

#include <vector>
#include <algorithm>

using namespace std;

int main() {
    vector<int> a = {1,2,3,7,1,5,4};
    vector<int> b = {6,7,4,3,3,1,7};
    a.erase(remove(a.begin(),a.end(),a[0]),a.end());
    b.erase(remove(b.begin(),b.end(),b[0]),b.end());

    return 1;
}

За този конкретен пример моята GNU gdb Ubuntu 7.7.1 посочва, че при връщане 1 ред: a = {2,3,7,1,5,4}, което не се очаква (изтрива само едно 1) и b = {7 ,4,3,3,1}, което не се очаква.

Моето очакване е b да бъде a=2,3,7,5,4 и b=7,4,3,3,1,7.

какво се случва тук


person user3064869    schedule 25.02.2015    source източник
comment
Не бихте ли очаквали първият да премахне всички 1 и да доведе до {2,3,7,5,4};?   -  person juanchopanza    schedule 25.02.2015
comment
Защо първият резултат е според очакванията? Не трябва ли да е 2,3,7,5,4 (и двете 1 се премахват)? Вярвам, че нарушавате някакво предварително условие, като предавате препратка към член на vector, който итерирате. И двата реда могат да бъдат коригирани чрез копиране - +a[0] и +b[0]   -  person Praetorian    schedule 25.02.2015
comment
Това всъщност е доста близо до SSCCE. Нуждае се само от изходния код.   -  person Fred Larson    schedule 25.02.2015
comment
първият коментар е толкова верен, но наистина очакваното поведение няма смисъл, така че предполагам, че това е стандартен въпрос?   -  person Guiroux    schedule 25.02.2015
comment
Това всъщност е вярно, не забелязах, че първият също е грешен.   -  person user3064869    schedule 25.02.2015
comment
@Praetorian Мога да разбера желанието ви за сбитост, но използването на унарен + за това не граничи ли с объркване? (На теория int(a[0]) също трябва да работи; резултатът е rvalue и използването му за инициализиране на препратка не трябва да води до псевдоним на която и да е съществуваща стойност, където и да е. Не знам дали ще се чувствам комфортно да разчитам на компилаторът обаче не оптимизира това. И това също не е по-ясно; разграничението lvalue-rvalue често е много фино.)   -  person James Kanze    schedule 25.02.2015
comment
@JamesKanze Разбира се, че е така, и унарните + и int(a[0]) попадат в една и съща кофа за обфускиране и бих се разплакал, ако видя код като този в преглед например. Но когато публикувам коментар на SO ... по-малко натискания на клавиши FTW :) Ако вместо това публикувах отговор, щях да съхраня стойността във временна променлива, както е показано по-долу.   -  person Praetorian    schedule 25.02.2015
comment
Защо return 1 вместо 0?   -  person Lightness Races in Orbit    schedule 25.02.2015


Отговори (1)


Декларацията на std::remove() изглежда така

template <class ForwardIterator, class T>
  ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val);

Имайте предвид, че последният параметър е препратка. Така след компилация той ефективно предава адреса на посочения елемент.

Чрез remove(a.begin(), a.end(), a[0]) се предава нещо, указващо адреса на 0-ия елемент от a. Когато remove() работи, след като 0-ият елемент бъде обработен, стойността, посочена от предадената препратка, се променя, което води до неочакван резултат.

За да получите очаквания резултат, направете копие, преди да се обадите на std::remove().

int toberemoved = a[0];
a.erase(remove(a.begin(),a.end(),toberemoved),a.end());
person timrau    schedule 25.02.2015
comment
Това беше и моята първа реакция. Но стандартът не поставя това изискване. Което означава, че внедряването на std::remove трябва да вземе необходимите предпазни мерки и ако той има проблема, който описвате, това е грешка в библиотеката. Или може би в стандарта, защото може би намерението е било това да не се изисква да работи. - person James Kanze; 25.02.2015
comment
Моля, не разпространявайте мита, че абстрактната машина, дефинирана от стандарта C++, прави някаква връзка между препратките и адресите на паметта. - person Lightness Races in Orbit; 25.02.2015
comment
@JamesKanze Не съм сигурен, че трябва изрично да направи това изискване. std::remove променя стойностите, съхранени на различни места на итерация. Такива промени променят стойността, посочена в последния параметър. Тъй като std::remove е посочено по отношение на == на value, фактът, че сте му казали да промени стойността на value означава, че ако == върне нещо различно, това е проблемът на извикващите, а не на алгоритмите. Докато извършва валидни операции (т.е. премахва елементи от диапазона по стабилен начин) и извиква ==, изпълнението на remove следва стандарта. - person Yakk - Adam Nevraumont; 25.02.2015
comment
@Yakk Аз самият не съм сигурен; стандартът наистина трябва да го изясни, може би чрез някакво глобално изявление относно аргументите, предавани чрез препратка. Фактът, че стандартът прави в други случаи уточнява липсата на псевдоним като изискване, подсказва, че при липса на такова специфично изискване потребителят има право да очаква, че то работи. - person James Kanze; 25.02.2015