在下面的程序中,当不使用mutable
时,程序编译失败。
#include <iostream>
#include <queue>
#include <functional>
std::queue<std::function<void()>> q;
template<typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
{
//q.emplace([=]() { // this fails
q.emplace([=]() mutable { //this works
func(std::forward<Args>(args)...);
});
}
int main()
{
auto f1 = [](int a, int b) { std::cout << a << b << "\n"; };
auto f2 = [](double a, double b) { std::cout << a << b << "\n";};
enqueue(f1, 10, 20);
enqueue(f2, 3.14, 2.14);
return 0;
}
这是编译器错误
lmbfwd.cpp: In instantiation of ‘enqueue(T&&, Args&& ...)::<lambda()> [with T = main()::<lambda(int, int)>&; Args = {int, int}]’:
lmbfwd.cpp:11:27: required from ‘struct enqueue(T&&, Args&& ...) [with T = main()::<lambda(int, int)>&; Args = {int, int}]::<lambda()>’
lmbfwd.cpp:10:2: required from ‘void enqueue(T&&, Args&& ...) [with T = main()::<lambda(int, int)>&; Args = {int, int}]’
lmbfwd.cpp:18:20: required from here
lmbfwd.cpp:11:26: error: no matching function for call to ‘forward<int>(const int&)’
func(std::forward<Args>(args)...);
我无法理解为什么没有 mutable
参数转发会失败。
此外,如果我传递带有字符串作为参数的 lambda,则不需要 mutable
并且程序可以正常工作。
#include <iostream>
#include <queue>
#include <functional>
std::queue<std::function<void()>> q;
template<typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
{
//works without mutable
q.emplace([=]() {
func(std::forward<Args>(args)...);
});
}
void dequeue()
{
while (!q.empty()) {
auto f = std::move(q.front());
q.pop();
f();
}
}
int main()
{
auto f3 = [](std::string s) { std::cout << s << "\n"; };
enqueue(f3, "Hello");
dequeue();
return 0;
}
为什么在 int double
的情况下需要 mutable 而在 string
的情况下不需要?这两者有什么区别?
最佳答案
非mutable
lambda 生成一个 闭包类型,在其 operator()
上带有隐式 const
限定符重载。
std::forward
是条件移动:当提供的模板参数不是左值引用。定义如下:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
(参见:https://en.cppreference.com/w/cpp/utility/forward)。
让我们将您的代码段简化为:
#include <utility>
template <typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
{
[=] { func(std::forward<Args>(args)...); };
}
int main()
{
enqueue([](int) {}, 10);
}
clang++ 8.x
产生的错误是:
error: no matching function for call to 'forward' [=] { func(std::forward<Args>(args)...); }; ^~~~~~~~~~~~~~~~~~ note: in instantiation of function template specialization 'enqueue<(lambda at wtf.cpp:11:13), int>' requested here enqueue([](int) {}, 10); ^ note: candidate function template not viable: 1st argument ('const int') would lose const qualifier forward(typename std::remove_reference<_Tp>::type& __t) noexcept ^ note: candidate function template not viable: 1st argument ('const int') would lose const qualifier forward(typename std::remove_reference<_Tp>::type&& __t) noexcept ^
在上面的片段中:
Args
是int
,指的是 lambda 之外的类型。args
指的是通过 lambda 捕获合成的闭包的成员,由于缺少mutable
而为const
。
因此 std::forward
调用是...
std::forward<int>(/* `const int&` member of closure */)
...与任何现有的 std::forward
重载都不匹配。提供给 forward
的模板参数与其函数参数类型不匹配。
将 mutable
添加到 lambda 使 args
非 const
,并找到合适的 forward
重载(第一个,它移动了它的论点)。
通过使用C++20 pack-expansion capture来“重写”args
的名字,我们可以避免上面提到的不匹配,使得代码即使没有mutable
也能编译>:
template <typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
{
[func, ...xs = args] { func(std::forward<decltype(xs)>(xs)...); };
}
Why is
mutable
required in case ofint double
and not in case ofstring
? What is the difference between these two ?
这是一个有趣的 - 它之所以有效,是因为您实际上并没有在调用中传递 std::string
:
enqueue(f3, "Hello");
// ^~~~~~~
// const char*
如果您将传递给 enqueue
的参数类型正确匹配到 f3
接受的参数类型,它将按预期停止工作(除非您使用 mutable
或 C++20 特性):
enqueue(f3, std::string{"Hello"});
// Compile-time error.
为了解释为什么带有 const char*
的版本可以工作,让我们再看一个简化的例子:
template <typename T>
void enqueue(T&& func, const char (&arg)[6])
{
[=] { func(std::forward<const char*>(arg)); };
}
int main()
{
enqueue([](std::string) {}, "Hello");
}
Args
推导出为 const char(&)[6]
。有一个匹配的 forward
重载:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
替换后:
template< class T >
constexpr const char*&& forward( const char*&& t ) noexcept;
这只是返回t
,然后用于构造std::string
。
关于c++ - 为什么不能在没有可变参数的情况下在 lambda 内部转发参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55992834/