为啥 lambda auto& 参数选择 const 重载?
Posted
技术标签:
【中文标题】为啥 lambda auto& 参数选择 const 重载?【英文标题】:Why does lambda auto& parameter choose const overload?为什么 lambda auto& 参数选择 const 重载? 【发布时间】:2019-07-23 15:49:24 【问题描述】:我正在尝试实现一个包装任意类型和互斥锁的类。要访问包装的数据,需要传递一个函数对象作为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_datastd::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 时按预期工作?
【问题讨论】:
@YSC lambda,通用或其他,是一个类,而不是类模板。F
很高兴地解决了那个类。类具有成员模板的事实在这一点上是无关紧要的。
【参考方案1】:
这从根本上是 SFINAE 不友好的可调用对象所发生的问题。如需更多参考,请查看P0826。
问题是,当你调用这个时:
str.locked([](auto &s) s = "Bar"; );
我们有 两个 locked
的重载,我们必须同时尝试。非const
重载工作正常。但是const
一个——即使它不会被重载决议选择——仍然必须被实例化(它是一个通用的 lambda,所以要弄清楚decltype(std::forward<F>(f)(m_data))
可能是什么,你必须实例化它)和那个实例化在 lambda 体内失败。正文在直接上下文之外,所以这不是替换失败——这是一个硬错误。
当你调用它时:
str.locked([](std::string& s) s = "Bar"; );
在重载解决的整个过程中,我们根本不需要查看正文——我们可以在调用站点简单地拒绝(因为您不能将const string
传递给string&
)。
今天的语言中并没有真正解决这个问题 - 您基本上必须在您的 lambda 上添加约束,以确保实例化失败发生在替换的直接上下文中,而不是在主体中。比如:
str.locked([](auto &s) -> void
s = "Bar";
);
请注意,我们不需要使这个 SFINAE 友好 - 我们只需要确保我们可以在不实例化主体的情况下确定返回类型。
更彻底的语言解决方案是允许“推导this
”(有关此特定问题的论文,请参阅the section)。但这不会出现在 C++20 中。
【讨论】:
这很可惜。 +1,因为这让我真的摸不着头脑。我不知道它为什么调用 const 版本,正如你指出的那样,它确实不是,它只需要检查,检查会导致硬错误。 感谢您的解释。你能详细说明一下“身体在直接上下文之外”吗? (我不清楚具体的上下文是什么) 感谢您的解释和解决方法。它可以工作,但由于它比编写类型更冗长,我将这样做而不是在这种情况下使用auto
。我也去看看论文。
如果你将参数作为auto&&
参数,它会起作用吗(虽然你可以传递你并不想允许的右值)?
@Unda 是的,如果你想像 locked(str, []...)
那样称呼它。只是改变语法。以上是关于为啥 lambda auto& 参数选择 const 重载?的主要内容,如果未能解决你的问题,请参考以下文章
错误:在 lambda 参数声明中使用 'auto' 仅适用于 -std=c++1y 或 -std=gnu++1y [-Werror]
为啥我不能在 lambda 中捕获这个引用('&this')?