为什么lambda auto¶meter选择const重载?
Why does lambda auto& parameter choose const overload?
我正在尝试实现一个 class,它包装了一个任意类型和一个互斥量。要访问包装数据,需要传递一个函数对象作为 locked
方法的参数。包装器 class 然后会将包装的数据作为参数传递给此函数对象。
我希望我的包装器 class 能够与常量和非常量一起使用,所以我尝试了以下方法
#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 不友好的可调用文件上。如需更多参考,请查看 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 友好 - 我们只需要确保我们可以确定 return 类型而不实例化主体。
一个更彻底的语言解决方案应该是允许 "Deducing this
"(请参阅有关此特定问题的论文中的 the section)。但这不会出现在 C++20 中。
我正在尝试实现一个 class,它包装了一个任意类型和一个互斥量。要访问包装数据,需要传递一个函数对象作为 locked
方法的参数。包装器 class 然后会将包装的数据作为参数传递给此函数对象。
我希望我的包装器 class 能够与常量和非常量一起使用,所以我尝试了以下方法
#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 不友好的可调用文件上。如需更多参考,请查看 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 友好 - 我们只需要确保我们可以确定 return 类型而不实例化主体。
一个更彻底的语言解决方案应该是允许 "Deducing this
"(请参阅有关此特定问题的论文中的 the section)。但这不会出现在 C++20 中。