为什么 lambda 自动和参数选择常量重载?

Why does lambda auto& parameter choose const overload?

本文关键字:选择 常量 重载 参数 lambda 为什么      更新时间:2023-10-16

我正在尝试实现包裹任意类型和静音的类。要访问包装数据,需要将功能对象作为locked方法的参数传递。然后,包装器类将把包装的数据作为参数传递给此函数对象。

我希望我的包装班与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;
}

使用通用lambda的第一个locked呼叫未能与以下错误一起编译

/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)
       ^

但是第二个带有std::string&参数的调用很好。

为什么?是否有一种方法可以使其在使用通用lambda时按预期工作?

这与sfinae nofryly callables发生的事情从根本上是一个问题。有关更多参考,请查看P0826。

问题是,当您致电时:

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

我们有locked的两个超载,我们必须同时尝试两者。非const过载正常。但是const One&ndash;即使不会通过超载分辨率选择它,否则仍然必须实例化(这是一个通用的lambda,因此要弄清楚您可能必须实例化的decltype(std::forward<F>(f)(m_data))),并且实例化在lambda主体内失败。身体不在直接的背景下,因此不是替代失败&ndash;这是一个困难的错误。

当您致电时:

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

在整个超负荷分辨率的过程中,我们根本不需要查看身体。我们可以在呼叫网站上拒绝(因为您不能将const string传递到string&中)。

今天的语言中没有真正解决这个问题的解决方案;基本上,您必须在lambda上添加约束,以确保实例化故障发生在替换的直接背景下而不是在体内。类似:

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

请注意,我们不需要使此Sfinae友好 - 我们只需要确保我们可以在不实例化身体的情况下确定返回类型。


一种更彻底的语言解决方案是允许"推论this"(请参阅有关此特定问题的论文中的一部分)。但这不会在C 20中。