丢弃限定符未知原因 (std::bind() / lambda)

Discards qualifiers unknown cause (std::bind() / lambda)

我不明白 where/why 限定词丢弃

#include <iostream>
#include <memory>

class A {
public:
    void f() {};
};

template <typename Callable>
void invoker(Callable c) {
    auto l = [=]() {
        c(); // <------------------- Error
    };
    l();
}

int main() {
    A a;
    invoker(std::bind(&A::f, a));
    return 0;
}

我在 c(); 行遇到编译器错误:

error: passing ‘const std::_Bind(A)>’ as ‘this’ argument of ‘_Result std::_Bind<_Functor(_Bound_args ...)>::operator()(_Args&& ...) [with _Args = {}; _Result = void; _Functor = std::_Mem_fn; _Bound_args = {A}]’ discards qualifiers [-fpermissive] c();

我不明白的解决方法:

g++ 版本:

g++ --version
g++ (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4

根据this

The return type of std::bind holds a member object of type std::decay<F>::type constructed from std::forward<F>(f), and one object per each of args..., of type std::decay<Arg_i>::type, similarly constructed from std::forward<Arg_i>(arg_i).

这意味着 c 拥有 a 的副本(我们称之为 c.a),但是由于您通过副本捕获 c,因此 c 在 lambda 内部是 const 限定的,并且 c.a "inherits" 在调用 &A::fc 的常量(参见本答案的末尾)。

  • 使 f() 成为 const 成员函数允许您在 const 对象上调用它。
  • 如果您使用 std::ref(a)c 不会保留 a 的副本,而是 std::reference_wrapper<A>,并且 operator() 的工作方式适用于 std::reference_wrapper<A> 不同(见本答案结尾)。
  • 当通过引用捕获 [&] 时,c 不再是 const,因此您可以在 c.a.[=94= 上调用非常量成员函数]

额外的细节(仍然来自 this):

If the stored argument arg is of type std::reference_wrapper<T> (for example, std::ref or std::cref was used in the initial call to bind), then the argument vn in the std::invoke call above is arg.get() and the type Vn in the same call is T&: the stored argument is passed by reference into the invoked function object.

所以调用 std::ref(a) 等同于:

std::invoke(&A::f, std::forward<A&>(c_a.get()));

其中 c_a 是存储在 c 中的 std::reference_wrapper<A>。注意转发类型是A&,不是A const&.

Otherwise, the ordinary stored argument arg is passed to the invokable object as lvalue argument: the argument vn in the std::invoke call above is simply arg and the corresponding type Vn is T cv &, where cv is the same cv-qualification as that of g.

所以原来的调用等价于(因为c是const,所以cvconst):

std::invoke(&A::f, std::forward<A const&>(c_a));

其中 c_acA 的副本。

霍尔特回答得很好。

如果您无法将 A::f 修改为 const 方法,您可以使 lambda 可变:

#include <iostream>
#include <memory>
#include <functional>

class A {
public:
    void f() {};
};

template <typename Callable>
void invoker(Callable c) {
    auto l = [=]() mutable // <-- Fixed
    {
        c(); 
    };
    l();
}

int main() {
    A a;
    invoker(std::bind(&A::f, a));
    return 0;
}