Search code examples
c++c++11lambdastdbindqualifiers

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


I don't understand where/why the qualifiers gets discarded.

#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;
}

I get a compiler error on the c(); line:

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();

Workarounds I don't understand:

  • Define A::f() as const: void f() const {};

  • bind() a reference to instance a: invoker(std::bind(&A::f, std::ref(a)));.

  • Pass to lambda by ref: auto l = [&]() {

g++ version:

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

Solution

  • According to 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).

    This means that c holds a copy of a (let's call it c.a), but since you are capturing c by copy, c is const-qualified inside the lambda, and c.a "inherits" the constness of c when invoking &A::f (see end of this answer).

    • Making f() a const member function allows you to call it on a const object.
    • If you use std::ref(a), c does not hold a copy of a but a std::reference_wrapper<A>, and the way operator() works for std::reference_wrapper<A> is different (see end of this answer).
    • When capturing by reference [&], c is not const anymore, so you can call a non-const member function on c.a.

    Extra details (still from 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.

    So the call with std::ref(a) is equivalent to:

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

    Where c_a is the std::reference_wrapper<A> stored inside c. Notice that the forward type is A&, not 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.

    So the original call is equivalent to (because c is const, so cv is const):

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

    Where c_a is the copy of A inside c.