为什么不能调用带有自动&参数的常量可变lambda?

2022-05-16 00:00:00 lambda c++ c++20 typetraits static-assert
#include <type_traits>

int main()
{
    auto f1 = [](auto&) mutable {};
    static_assert(std::is_invocable_v<decltype(f1), int&>); // ok

    auto const f2 = [](auto&) {};
    static_assert(std::is_invocable_v<decltype(f2), int&>); // ok

    auto const f3 = [](auto&) mutable {};
    static_assert(std::is_invocable_v<decltype(f3), int&>); // failed
}

参见demo

为什么常量可变lambda不能接受引用参数?


解决方案

这里有两件有趣的事情。

首先,lambda的调用操作符(模板)默认为const。如果您提供mutable,则它不是constmutable对lambda的影响仅与正常成员函数中的尾随const的影响相反(它不影响lambda捕获等)

所以如果你看这个:

auto const f3 = [](auto&) mutable {};
static_assert(std::is_invocable_v<decltype(f3), int&>); // failed

这是一个const对象,其调用操作符模板(因为它是一个泛型lambda)是非const。因此您不能调用它,因为您不能在任何其他上下文中调用const对象上的非const成员函数。请参阅this other answer。

第二,有人指出,尽管如此,这是可行的:

auto const f4 = [](int&) mutable {}; // changed auto& to int&
static_assert(std::is_invocable_v<decltype(f4), int&>); // now ok

这是而不是编译器错误。也不是意味着我刚才说的是错的。f4仍然有非常数调用运算符。您无法调用它,因为f4是一个常量对象。

但是。

lambdas没有捕获的另一个有趣的方面是:它们有一个将函数转换为函数指针类型的函数。也就是说,我们通常认为lambdaf4是这样的:

struct __unique_f4 {
    auto operator()(int&) /* not const */ { }
};

如果这就是整个故事,const __unique_f4确实不能用int&调用。但它实际上看起来是这样的:

struct __unique_f4 {
    auto operator()(int&) /* not const */ { }

    // conversion function to the appropriate function
    // pointer type
    operator void(*)(int&)() const { /* ... */ }
};

我们有这样一个规则,当您调用一个对象时,例如f(x),您不仅要考虑f的调用运算符--那些名为operator()的成员--而且还要考虑f的任何surrogate call functions--是否有任何函数指针可以转换为f,然后调用。

在这种情况下,您可以!您可以将f4转换为void(*)(int&),该函数指针可通过int&调用。

但这仍然意味着f4的调用运算符不是const,因为您声明了它是可变的。而且它没有说明您是否可以让mutablelambdas获取引用参数。

相关文章