Date: Wed, 15 Sep 2021 23:05:06 +0200
Hello to everyone.
I hope this is the right mailing list, if not excuse me.
I wanted to gather opinions about lambda captures, and understand if it
would make sense to write a paper.
First of all, the use case.
Suppose I have a class that looks like like
I hope this is the right mailing list, if not excuse me.
I wanted to gather opinions about lambda captures, and understand if it
would make sense to write a paper.
First of all, the use case.
Suppose I have a class that looks like like
---- struct data{ std::string str = "Hello World!"; int i = -1; }; class data_and_mutex{ data d; // data protected by mutex mutable std::mutex m; public: template <class F> auto lock(F f){ std::scoped_lock lck(this->m); return f(this->d); } }; ---- Notice that it provides a lock member function for providing access to it's internal data, and at the same time ensure that the mutex is always locked. (I like this pattern as it is, for the user of the class, very hard to misuse by accident, and at the same time leaves great flexibility. For example it's the caller that decides the atomicity/granularity of the operations, without introducing an additional locking mechanisms.) This is one of the uses of the API: ---- bool foo(data_and_mutex& d_m){ return d_m.lock([](data& d){ bool res = d.str == get_value(); if(res){d.i++;} return res; } ); } ---- `get_value` is a function declared somewhere, we might even not control it, it's signature is one of the following, it should not matter which: ---- const std::string& get_value(); // or std::string get_value(); // could also be std::string& get_value(), but leaving out for simplicity ---- What happens inside function foo is suboptimal at least, possibly problematic, as I'm calling external code (get_value) while locking a mutex. A fix is simple ---- bool foo(data_and_mutex& d_m){ const auto& value = get_value(); return d_m.lock([&](data& d){ bool res = d.str == value; if(res){d.i++;} return res; } ); } ---- Note: * I'm aware that technical std::string::operator== is still external code, but I know/assume it's a pure/non-problematic function and won't lead to deadlocks or lead to problematic performance issues * `const auto&` (or `const std::string&`) works correctly with both possible signatures of get_value In this case, `foo` does not do much work, but we expanded a lot the scope of the variable returned by get_value. Fortunately, since c++14, we can capture the values returned by functions directly. Thus it is possible to write ---- bool foo(data_and_mutex& d_m){ return d_m.lock([value = get_value()](data& d){ bool res = d.str == value; if(res){d.i++;} return res; } ); } ---- This seems perfect, because get_value is called before holding the mutex, but... If the signature is "const std::string& get_value()", the we are creating an unnecessary copy(!), while in all previous snippets we did not. To avoid this copy, one needs to write (notice the &) ---- bool foo(data_and_mutex& d_m){ return d_m.lock([&value = get_value()](data& d) { bool res = d.str == value; if(res){d.i++;} return res; } ); } ---- But this code does not work with `std::string get_value();`, it fails to compile with "error: non-const lvalue reference to type 'basic_string<...>' cannot bind to a temporary of type 'basic_string<...>'" as it tries to make a mutable(!) reference. Unfortunately we cannot write something like ---- bool foo(data_and_mutex& d_m){ return d_m.lock([const& value = get_value()](data& d) { bool res = d.str == value; if(res){d.i++;} return res; } ); } ---- I know std::as_const is normally proposed as solution for similar issues, but * it does not compile for rvalues * even if it would compile, it does not enable the life extension by const-ref, and thus would create a dangling `value` On could do what std::as_const does by hand, ie casting to const ---- bool foo(data_and_mutex& d_m){ return d_m.lock([&value = static_cast<const std::string>(get_value())](data& d) { bool res = d.str == value; if(res){d.i++;} return res; } ); } ---- which works correctly, but it seems verbose, and makes it necessary to spell the type returned from get_value. thus making it possible to add accidental conversions. AFAIK, a macro is the only viable approach (as a function does not work) to reduce both verbosity and risk of unwanted conversions: ---- #define C_REF(...) static_cast<const decltype(__VA_ARGS__)&>(__VA_ARGS__) bool foo(data_and_mutex& d_m){ return d_m.lock([&value = C_REF(get_value())](data& d) { bool res = d.str == value; if(res){d.i++;} return res; } ); } ---- nevertheless the code is (slightly) more verbose (in my examples I've used only a get_value function, imagine having two or three) and is not as idiomatic. Using `const &` is a pattern we already use, and would thus feel more natural. Also introducing a macro seems/feels... wrong. (And once I have/need this macro... what value does std::as_const add?) So, to sum it up, I would like see the possibility to add const to the lambda capture. Just this qualifier (leaving thus volatile out), and not the type (it can be done independently if we acknowledge it might be a good idea). This would give us a "unified syntax" that works both for function returning values and references (both const and mutable), as long as we do not want to modify the value. This is actually what we already have when working with functions: * void foo(const std::string&); vs void foo(std::string); in both cases the user of foo can write foo(std::string("hello")); * std::string foo(); vs const std::string& foo(); in both cases the user can write std::string v = foo(); or(!) const std::string& v = foo(); (or use auto instead of std::string) The surrounding code normally does not need any modification because `const auto&` binds to values, so we can write the code once for both cases. I would like to know (supposing I'm on the correct mailing list) some opinions about the topic, and if it is worth writing and presenting a paper. (NOTE: I've only written a couple of papers for LEWG, I've read that writing papers and getting it accepted for core is more difficult) Best Federico
Received on 2021-09-15 16:08:48