Отбрасывает квалификаторы неизвестной причины (std :: bind () / lambda)

Я не понимаю, где и почему квалификаторы отбрасываются.

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

ошибка: передача 'const std :: _ Bind (A)>' как 'this' аргумента '_Result std :: _ Bind ‹_Functor (_Bound_args ...)> :: operator () (_ Args && ...) [с _Args = {}; _Result = void; _Functor = std :: _ Mem_fn; _Bound_args = {A}] ’отбрасывает квалификаторы [-fpermissive] c ();

Обходные пути, которые я не понимаю:

  • Определите A :: f () как const: void f () const {};

  • bind() ссылка на экземпляр a: invoker (std :: bind (& A :: f, std :: ref (a)));.

  • Перейти к lambda через ref: auto l = [&] () {

версия g ++:

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

person hudac    schedule 16.01.2018    source источник
comment
Вы захватываете по значению, когда вы это делаете, захваченные объекты не могут быть изменены. Чтобы разрешить это, вы должны либо захватить по ссылке, либо объявить лямбда как mutable   -  person Rerito    schedule 16.01.2018


Ответы (2)


Согласно это:

Тип возвращаемого значения std::bind содержит объект-член типа std::decay<F>::type, созданный из std::forward<F>(f), и по одному объекту на каждый из args... типа std::decay<Arg_i>::type, аналогичным образом созданный из std::forward<Arg_i>(arg_i).

Это означает, что c содержит копию a (назовем ее c.a), но поскольку вы захватываете c копией, c const квалифицируется внутри лямбда, а c.a "наследует" постоянство c при вызове &A::f (см. Конец этот ответ).

  • Создание f() функции-члена const позволяет вам вызывать ее для объекта const.
  • Если вы используете std::ref(a), c не содержит копию a, а std::reference_wrapper<A>, а способ, которым operator() работает для std::reference_wrapper<A>, отличается (см. Конец этого ответа).
  • При захвате по ссылке [&], c больше не const, поэтому вы можете вызвать неконстантную функцию-член на c.a.

Дополнительные сведения (взяты из this):

Если сохраненный аргумент arg имеет тип std::reference_wrapper<T> (например, std::ref или std::cref использовался при первоначальном вызове bind), то аргумент vn в приведенном выше вызове std::invoke - arg.get(), а тип Vn в том же вызове - T&: сохраненный Аргумент передается по ссылке в вызываемый объект функции.

Таким образом, вызов с std::ref(a) эквивалентен:

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

Где c_a - это std::reference_wrapper<A>, хранящийся внутри c. Обратите внимание, что тип пересылки - A&, а не A const&.

В противном случае обычный хранимый аргумент arg передается вызываемому объекту как аргумент lvalue: аргумент vn в приведенном выше вызове std::invoke - это просто arg, а соответствующий тип Vn - T cv &, где cv - это то же cv-квалификация, что и g.

Таким образом, исходный вызов эквивалентен (поскольку c является константой, поэтому cv равно const):

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

Где c_a - это копия A внутри c.

person Holt    schedule 16.01.2018

Отличный ответ Холта.

Если вы не можете изменить A :: f как константный метод, вы можете сделать лямбда изменяемой:

#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;
}
person Richard Hodges    schedule 16.01.2018