В этом примере экземпляр foo ничего не делает, кроме печати, независимо от того, сконструирован он копированием или перемещением.
#include <iostream>
#include <algorithm>
#include <vector>
struct foo {
foo()=default;
foo(foo &&) { std::cout << "move constructed\n"; }
foo(const foo &) { std::cout << "copy constructed\n"; }
};
int main()
{
foo x;
std::vector<int> v; // empty
std::remove_if (v.begin(), v.end(),
[x=std::move(x)](int i){ return false; });
}
Это дает следующий результат:
move constructed
copy constructed
move constructed
move constructed
copy constructed
copy constructed
Вопросов:
std::remove_if создает так много замыканий?Компилятор - gcc 8.1.1
Для сравнения, clang 6.0.0 производит только один (ожидаемый) "ход построен" (-O2 для clang и gcc).
MSVS 2018 также производит просто «построенный ход» как с отладочной, так и с выпускной сборками.





Если мы посмотрим на реализация std::remove_if в gcc libstdC++ - v3, мы заметим, что предикат передается вниз по цепочке вызовов (по значению, иногда) за несколько шагов до достижения самой нижней функции __find_if (используемой remove_if).
Посчитаем ходы и копии:
move constructed, когда предикат (включая захваченный x) отправляется по значению, но не как lvalue, в std::remove_if точка входа
copy constructed при передаче функции __gnu_cxx::__ops::__pred_iter(...), которая, в свою очередь:
вызывает _GLIBCXX_MOVE макрос, таким образом, move constructed,
который перемещает предикат в _Iter_pred ctor, который перемещает его (move constructed) в член _M_pred.
Вызов от std::remove_if к std::__remove_if кажется оптимизированным, поскольку _Iter_pred не является lvalue, я думаю, но __remove_if, в свою очередь, передает завернутый предикат по значению в std::__find_if для другого вызова copy constructed.
std::__find_if, в свою очередь, пересылает обернутый предикат по значению в еще одна перегрузка __find_if, который, наконец, является приемником этой цепочки вызовов и последним copy constructed.
Может быть интересно сравнить, например, с Clang реализацияstd::remove_if, поскольку clang (6.0.1) не создает эту цепочку перемещения-копирования для примера OP std::remove_if. Беглый взгляд показывает, что кажется, что clang использовать черты типа предикатов обязательно передает предикат как ссылка lvalue.
И clang, и gcc создают одинаковые цепочки move / copy для следующего надуманного примера, который показывает цепочку, аналогичную реализации gcc:
#include <iostream>
#include <utility>
struct foo {
foo() = default;
foo(foo &&) { std::cout << "move constructed\n"; }
foo(const foo &) { std::cout << "copy constructed\n"; }
};
template <typename Pred>
struct IterPred {
Pred m_pred;
explicit IterPred(Pred pred) : m_pred(std::move(pred)) {}
};
template <typename T>
inline IterPred<T> wrap_in_iterpred (T l) {
return IterPred<T>(std::move(l));
}
template <typename T>
void find_if_overload(T l) {
(void)l;
}
template <typename T>
void find_if_entrypoint(T l) {
find_if_overload(l);
}
template <typename T>
void remove_if_entrypoint(T l) {
find_if_entrypoint(
wrap_in_iterpred(l));
}
int main()
{
foo x;
remove_if_entrypoint([x=std::move(x)](int){ return false; });
}
Где и gcc (8.2.0), и clang (6.0.1) создают следующую цепочку:
move constructed
copy constructed
move constructed
move constructed
copy constructed
Спасибо .. интересный момент о clang, использующем трейты для передачи lvalue ref ... В добавленном вами примере, если вы компилируете с -fno-elide-constructors, вы можете увидеть полный набор конструкторов, как это изложено в исходном коде.
Потому что он ожидает, что функциональный объект будет свободно копировать? Редко есть причина не пропустить лямбды
[&].