【问题标题】:Why can't argument be forwarded inside lambda without mutable?为什么不能在没有可变参数的情况下在 lambda 内部转发参数?
【发布时间】:2019-09-23 08:37:41
【问题描述】:

在下面的程序中,当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 的情况下不需要?这两者有什么区别?

【问题讨论】:

  • 顺便说一句,在第二个示例中,您不是转发std::string,而是转发const char*。函数采用什么类型并不重要,而是将什么类型的参数传递给enqueue。我的猜测是,从一开始就是 const 的论点会有所不同,但我不确定如何。
  • 确实,it does fail 如果你做到了enqueue(f3, std::string("Hello"));
  • ...是,或"Hello"s(使用字符串文字)
  • @IgorTandetnik 方向是对的:如果您传递 const 参数,错误就会消失。参见例如godbolt.org/z/EdY4Mn,我刚刚介绍了一些空类Foo。如果你去掉Foo 的常量,编译失败。不过还是不知道为什么……
  • 意识到使用std::forward&lt;const Args&gt; 也可以修复编译错误可能很有启发性(尽管毫无意义)。

标签: c++ c++11 lambda language-lawyer


【解决方案1】:

mutable lambda 生成一个闭包类型,在其operator() 重载上带有隐式const 限定符。

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;

(参见:https://en.cppreference.com/w/cpp/utility/forward)。


让我们将您的 sn-p 简化为:

#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
    ^

在上面的sn-p中:

  • Argsint,指的是 lambda 之外的类型。

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

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

std::forward<int>(/* `const int&` member of closure */)

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

mutable 添加到 lambda 使argsconst,并找到合适的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)...); };
}

live example on godbolt.org


为什么在 int double 的情况下需要 mutable 而在 string 的情况下不需要?这两者有什么区别?

这很有趣 - 它之所以有效,是因为您实际上并没有在调用中传递 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(&amp;)[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

【讨论】:

  • 哇,这是一个很好的答案!您能否添加一些对标准的引用?
  • @andreee:改进了我的答案,如果清楚请告诉我。
  • 感谢您的解释。我有一个疑问,如果是 const char *,我们还有一个 const args.. 仍然有效.. 为什么会这样?
  • @bornfree:添加了解释。
  • const char* 来自哪里?他传递了一个参考,所以Argsconst char(&amp;)[6] 所以它是std::forward&lt;const char(&amp;)[6]&gt;(arg)
猜你喜欢
  • 2014-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-10
  • 2015-08-24
  • 1970-01-01
  • 2022-01-15
相关资源
最近更新 更多