Если мы посмотрим на реализацию std::remove_if
в libstdc ++ gcc - 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