为什么lambda auto&parameter选择const重载?

Und*_*nda 25 c++ templates generic-lambda c++14

我正在尝试实现一个包含任意类型和互斥锁的类.要访问包装数据,需要传递一个仿函数作为locked方法的参数.然后包装类将包装的数据作为参数传递给仿函数.

我希望我的包装类使用const和非const,所以我尝试了以下内容

#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;
}
Run Code Online (Sandbox Code Playgroud)

locked使用通用lambda 的第一次调用无法编译,并出现以下错误

/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)
       ^
Run Code Online (Sandbox Code Playgroud)

但是std::string&参数的第二次调用很好.

这是为什么 ?有没有办法让它在使用通用lambda时按预期工作?

Bar*_*rry 31

这与SFINAE不友好的可赎回事件所发生的事情基本上是一个问题.有关更多参考,请查看P0826.

问题是,当你这样称呼时:

 str.locked([](auto &s) { s = "Bar"; });
Run Code Online (Sandbox Code Playgroud)

我们有两个重载,locked我们必须尝试两个.非const过载工作正常.但是那个const- 即使它不会被重载决策选中 - 仍然需要实例化(它是一个通用的lambda,所以要弄清楚你decltype(std::forward<F>(f)(m_data))可能需要实例化它)并且实例化在实体中失败了.拉姆达.身体在直接上下文之外,所以它不是替换失败 - 这是一个很难的错误.

当你这样称呼时:

str.locked([](std::string& s) { s = "Bar"; });
Run Code Online (Sandbox Code Playgroud)

在重载解析的整个过程中我们根本不需要查看正文 - 我们可以简单地在呼叫站点拒绝(因为你不能将其const string传入a string&).

在今天的语言中,这个问题并没有真正的解决方案 - 你基本上必须在lambda上添加约束,以确保实例化失败发生在替换的直接上下文中而不是在正文中.就像是:

str.locked([](auto &s) -> void {
    s = "Bar";
});
Run Code Online (Sandbox Code Playgroud)

请注意,我们不需要使这个SFINAE友好 - 我们只需要确保我们可以在不实例化主体的情况下确定返回类型.


更彻底的语言的解决办法是允许"推导this"(见一节中一篇关于这个具体问题).但这不会出现在C++ 20中.

  • 这是一种耻辱.+1因为这让我真的摸不着头脑.我无法弄清楚它为什么调用const版本,而且你指出它实际上不是,它只需要检查,并且检查会导致硬错误. (7认同)
  • @Unda是的,如果您想像是`locked(str,[] {...})``一样来调用它。只需更改语法。 (2认同)