Here is the best way I've found. Improvements from original include
- definition of the lambda generating function
a) Universal (aka forwarding) reference. Previously I had a const && which causes a silent copies.
b) a std::forward to a capture by value. Importantly this avoids dangling references when using lambda returned from this functiontemplate <typename T> auto my_test(T &&s) { return [p = std::forward<T>(s)](int i) { return true; }; }
c) using std::move when constructing filters in main function. Or, one could construct filters with rvalues instead of std::moving lvalues in main()
2) better diagnostics in struct S, by RLWA32-6355
3) addition of move constructor suggested by RLWA32-6355.
here is the whole thing with some refactoring to more descriptive names
#include <iostream>
#include <ranges>
#include <vector>
#include <utility>
using namespace std;
using std::ranges::views::filter;
// This definition is the same as struct S before. skip over if you like
struct PredData {
PredData() = delete;
PredData(int i) : m_id(i) {
cout << "\ns(" << m_id << ")";
ctors++;
}
PredData(const PredData &s) {
m_id = s.m_id;
cout << "\ncopy s(" << m_id << ")";
copy_ctors++;
}
// Comment/uncomment to see effect of the move constructor when lambda captures by value
PredData(PredData &&rhs) {
swap(m_id, rhs.m_id);
cout << "\nmove s(" << m_id << ")";
move_ctors++;
}
\~PredData() {
cout << "\n\~s(" << m_id << ")";
dtors++;
}
operator int() const {
return m_id;
}
static int ctors, dtors, copy_ctors, move_ctors;
int m_id{-1};
};
int PredData::ctors{0}, PredData::dtors{0}, PredData::copy_ctors{0}, PredData::move_ctors{0};
// Change lambda to capture by reference [&s] to see effect on constructor use
template <typename T>
constexpr inline auto pred_generator(T &&temp_data) {
return [pred_data = std::forward<T>(temp_data)](int i) {
cout << "\nIn lambda: value is " << i << " id is " << (int)pred_data;
return i <= (int)pred_data;
};
}
int main() {
int expt_id = 0;
std::vector<int> ints{1, 2, 3, 4, 5};
{ // Scope to invoke destructors before totals printed
PredData s1{++expt_id}, s2{++expt_id};
auto f1 = pred_generator(s1); // two ctor, one move
auto f2 = pred_generator(std::move(s2)); // one ctor, one move
auto f3 = pred_generator(PredData{++expt_id}); // one ctor, one move
cout << "\nfilter";
auto g0 = filter(f1); // s(1) 1 copy, 1 move
// s(-1) 1 dtor
cout << "\nints | filter1";
auto g1 = ints | filter(f1); // s(1) 1 copy, 3 moves
// s(-1) 3 dtor
cout << "\nints | filter1 | filter2";
auto g2 = ints | filter(f1) | filter(f2);
// s(1) 1 copy, 5 moves
// s(2) 1 copy, 3 moves
// s(-1), 8 dtor
cout << "\nints | filter1 | filter2 | filter3";
auto g3 = ints | filter(f1) | filter(f2) | filter(f3);
// s(1) 1 copy, 7 moves
// s(2) 1 copy, 5 moves
// s(3) 1 copy, 3 moves
// s(-1), 15 dtor
cout << "\nints | filter(move) ";
auto g4 = ints | filter(std::move(f1)) ;
// s(1), 4 moves
// s(-1), 3 dtors
cout << "\nBefore filtered for";
for (auto &x : g3) {
cout << "\nx is " << x;
}
cout << "\nAfter filtered for";
}
cout << "\nctors " << PredData::ctors
<< ", copy_ctors " << PredData::copy_ctors
<< ", move_ctors " << PredData::move_ctors
<< ", dtors " << PredData::dtors;
return 0;
}