为什么不能在不可变的情况下在lambda中转发参数?

bor*_*ree 15 c++ lambda language-lawyer c++11

在下面的程序中,当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;
}
Run Code Online (Sandbox Code Playgroud)

这是编译器错误

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

我无法理解为什么不使用参数转发会失败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;
}
Run Code Online (Sandbox Code Playgroud)

为什么在情况下需要可变的而在情况下则不需要可变int doublestring?两者有什么区别?

Vit*_*meo 19

mutablelambda会在其重载时生成带有隐式限定符的闭包类型constoperator()

std::forward是有条件的移动:等效于std::move提供的模板参数不是左值引用时。定义如下:

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

(请参阅: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);
}
Run Code Online (Sandbox Code Playgroud)

由产生的错误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
    ^
Run Code Online (Sandbox Code Playgroud)

在上面的代码段中:

  • Argsint并且是指lambda之外的类型。

  • args指通过lambda捕获合成的闭包成员,并且是const由于缺少mutable

因此,std::forward调用是...

std::forward<int>(/* `const int&` member of closure */)
Run Code Online (Sandbox Code Playgroud)

...不符合的任何现有重载std::forward。提供给的模板参数forward与其函数参数类型之间不匹配。

添加mutable到lambda使得argsnon- const,并且forward找到了合适的重载(第一个重载了它的参数)。


通过使用C ++ 20压缩扩展捕获来“重写”的名称args,我们可以避免上面提到的不匹配,即使没有mutable以下内容,也可以编译代码:

template <typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
{
    [func, ...xs = args] { func(std::forward<decltype(xs)>(xs)...); };
}
Run Code Online (Sandbox Code Playgroud)

Godbolt.org上的实时示例


为什么mutable需要,int double而不是string?两者有什么区别?

这很有趣-之所以有效,是因为您实际上并未std::string在调用中传递a :

enqueue(f3, "Hello");
//          ^~~~~~~
//          const char*
Run Code Online (Sandbox Code Playgroud)

如果您正确地将传递给的参数的类型与enqueue被接受的参数匹配f3,则它将按预期停止工作(除非您使用mutable或C ++ 20功能):

enqueue(f3, std::string{"Hello"});
// Compile-time error.
Run Code Online (Sandbox Code Playgroud)

为了解释带有该版本的版本为什么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");
}
Run Code Online (Sandbox Code Playgroud)

Args推导为const char(&)[6]。有一个匹配的forward重载:

template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
Run Code Online (Sandbox Code Playgroud)

替换后:

template< class T >
constexpr const char*&& forward( const char*&& t ) noexcept;
Run Code Online (Sandbox Code Playgroud)

这只是返回t,然后用于构造std::string

  • 哇,这是一个很好的答案!您能否在标准星标中添加一些参考? (2认同)
  • @andreee:改进了我的答案,让我知道是否清楚。 (2认同)
  • @bornfree:添加了说明。 (2认同)