Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does lambda auto& parameter choose const overload?

Tags:

I'm trying to implement a class which wraps an arbitrary type and a mutex. To access the wrapped data, one needs to pass a function object as parameter of the locked method. The wrapper class will then pass the wrapped data as parameter to this function object.

I'd like my wrapper class to work with const & non-const, so I tried the following

#include <mutex> #include <string>  template<typename T, typename Mutex = std::mutex> class   Mutexed { private:     T m_data;     mutable Mutex m_mutex;  public:     using type = T;     using mutex_type = Mutex;  public:     explicit Mutexed() = default;      template<typename... Args>     explicit Mutexed(Args&&... args)         : m_data{std::forward<Args>(args)...}     {}      template<typename F>     auto locked(F&& f) -> decltype(std::forward<F>(f)(m_data)) {         std::lock_guard<Mutex> lock(m_mutex);         return std::forward<F>(f)(m_data);     }      template<typename F>     auto locked(F&& f) const -> decltype(std::forward<F>(f)(m_data)) {         std::lock_guard<Mutex> lock(m_mutex);         return std::forward<F>(f)(m_data);     } };  int main() {     Mutexed<std::string> str{"Foo"};      str.locked([](auto &s) { /* this doesn't compile */         s = "Bar";     });      str.locked([](std::string& s) { /* this compiles fine */         s = "Baz";     });     return 0; } 

The first locked call with the generic lambda fails to compile with the following error

/home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp: In instantiation of ‘main()::<lambda(auto:1&)> [with auto:1 = const std::__cxx11::basic_string<char>]’: /home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp:30:60:   required by substitution of ‘template<class F> decltype (forward<F>(f)(((const Mutexed<T, Mutex>*)this)->Mutexed<T, Mutex>::m_data)) Mutexed<T, Mutex>::locked(F&&) const [with F = main()::<lambda(auto:1&)>]’ /home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp:42:6:   required from here /home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp:41:11: error: passing ‘const std::__cxx11::basic_string<char>’ as ‘this’ argument discards qualifiers [-fpermissive]          s = "Bar";            ^ In file included from /usr/include/c++/5/string:52:0,                  from /usr/include/c++/5/stdexcept:39,                  from /usr/include/c++/5/array:38,                  from /usr/include/c++/5/tuple:39,                  from /usr/include/c++/5/mutex:38,                  from /home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp:1: /usr/include/c++/5/bits/basic_string.h:558:7: note:   in call to ‘std::__cxx11::basic_string<_CharT, _Traits, _Alloc>& std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::operator=(const _CharT*) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’        operator=(const _CharT* __s)        ^ 

But the second call with the std::string& parameter is fine.

Why is that ? And is there a way to make it work as expected while using a generic lambda ?

like image 832
Unda Avatar asked Mar 01 '19 15:03

Unda


People also ask

What is a lambda sensor in a car?

The lambda sensor, also called an oxygen sensor, is a small probe located on the car exhaust, between the exhaust manifold and the catalytic converter. It was developed by Volvo in the 70s.

What is a lambda function?

A lambda function is another name for an anonymous function - essentially a function without a name. Usually you use this in languages where you will only need to use the function once. For example instead of Not the answer you're looking for?

What is lambda regulation in petrol engines?

Petrol engines reach their maximum power when a lack of air 5-15% (L = 0.85 - 0.95) is present, and a minimum fuel consumption is achieved with an excess air of 10 – 20% (L = 1.1 – 1.2). Thereby when the engine is working, the proportion L is constantly varying in the range 0.9 - 1.1 and this is the lambda regulation operating range.

How do you find the lambda value of an exhaust sensor?

The lambda value is calculated by looking at changes in the composition of exhaust gas over 60 seconds. You can also use a multimeter. Connect it parallel to the signal line of the sensor andand set it to 1V or 2V. When you start your engine, a reading between 0.4–0.6V should appear.


1 Answers

This is a problem fundamentally with what happens with SFINAE-unfriendly callables. For more reference, check out P0826.

The problem is, when you call this:

 str.locked([](auto &s) { s = "Bar"; }); 

We have two overloads of locked and we have to try both. The non-const overload works fine. But the const one – even if it won't be selected by overload resolution anyway – still has to be instantiated (it's a generic lambda, so to figure out what decltype(std::forward<F>(f)(m_data)) might be you have to instantiate it) and that instantiation fails within the body of the lambda. The body is outside of the immediate context, so it's not a substitution failure – it's a hard error.

When you call this:

str.locked([](std::string& s) { s = "Bar"; }); 

We don't need to look at the body at all during the whole process of overload resolution – we can simply reject at the call site (since you can't pass a const string into a string&).

There's not really a solution to this problem in the language today – you basically have to add constraints on your lambda to ensure that the instantiation failure happens in the immediate context of substitution rather than in the body. Something like:

str.locked([](auto &s) -> void {     s = "Bar"; }); 

Note that we don't need to make this SFINAE-friendly - we just need to ensure that we can determine the return type without instantiating the body.


A more thorough language solution would have been to allow for "Deducing this" (see the section in the paper about this specific problem). But that won't be in C++20.

like image 153
Barry Avatar answered Oct 14 '22 04:10

Barry