Подтвердить что ты не робот

Почему невозможно использовать частный метод в лямбда?

Наличие класса, подобного этому:

class A {
public:
    bool hasGrandChild() const;

private:
    bool hasChild() const;
    vector<A> children_;
};

Почему не возможно использовать закрытый метод hasChild() в выражении лямбда, определенном в методе hasGrandChild(), как это?

bool A::hasGrandChild() const {
    return any_of(children_.begin(), children_.end(), [](A const &a) {
        return a.hasChild();
    });
}

Компилятор выдает ошибку, что метод hasChild() является конфиденциальным в контексте. Есть ли способ обхода?

Edit: Кажется, что код, который я опубликовал, изначально работает. Я думал, что это эквивалентно, но код, который не работает в GCC, больше похож на это:

#include <vector>
#include <algorithm>

class Foo;

class BaseA {
protected:
    bool hasChild() const { return !children_.empty(); }
    std::vector<Foo> children_;
};

class BaseB {
protected:
    bool hasChild() const { return false; }
};

class Foo : public BaseA, public BaseB {
public:
  bool hasGrandChild() const {
    return std::any_of(children_.begin(), children_.end(), [](Foo const &foo) {
        return foo.BaseA::hasChild();
      });
  }  
};

int main()
{
  Foo foo;
  foo.hasGrandChild();
  return 0;
}

Кажется, что существует проблема с полностью квалифицированными именами как это не работает, но это работает.

4b9b3361

Ответ 1

Кажется, это просто ошибка GCC в специальном случае, когда лямбда пытается получить доступ к защищенному члену из родительского класса, используя полное имя. Это не работает:

class Base {
protected:
    bool hasChild() const { return !childs_.empty(); }
    std::vector<Foo> childs_;
};

class Foo : public Base {
public:
  bool hasGrandChild() const {
    return std::any_of(childs_.begin(), childs_.end(), [](Foo const &foo) {
      return foo.Base::hasChild();
    });
  }  
};

но это работает:

class Foo : public Base {
public:
  bool hasGrandChild() const {
    return std::any_of(childs_.begin(), childs_.end(), [](Foo const &foo) {
      return foo.hasChild();
    });
  }  
};

Согласно С++ 11, 5.1.2/3:

Тип лямбда-выражения (который также является типом объект закрытия) - это уникальный, неназванный тип неединичного класса, называемый тип закрытия - свойства которого описаны ниже. Этот тип класса не является совокупностью (8.5.1). Тип закрытия объявляется в наименьший объем блока, класс или область пространства имен, которая содержит соответствующее лямбда-выражение.

И затем С++ 11, 11.7/1:

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

Таким образом, указанная функция-локальная лямбда должна иметь те же права доступа, что и любой другой член класса. Поэтому он должен иметь возможность вызывать защищенный метод из родительского класса.

Ответ 2

В стандарте (С++ 11, §5.1.2/3) указано, что

Тип лямбда-выражения (который также является типом объект закрытия) - это уникальный, неназванный тип неединичного класса, называемый тип замыкания.

Поскольку это уникальный тип класса, который не является friend A, он не имеет доступа к A частным членам.

Что здесь делает компилятор, это создать тип класса, в котором есть соответствующие элементы для хранения любых захваченных переменных, подходящий operator() и т.д. - именно это вы напишете сами, если хотите эмулировать lambdas на С++ 03, Этот тип, конечно, не имел бы доступа к членам private, что могло бы облегчить визуализацию, почему существует ограничение и почему нет обходного пути.

Обновить в отношении возможных обходных путей:

Лучше сказать "нет обходных решений, использующих лямбда", потому что в общем случае обходные пути существуют, хотя они требуют, чтобы вы отказались от удобного синтаксиса лямбда. Например, вы можете:

  • Введите локальный тип класса, который явно отображает this вместе с любыми другими локалями, которых он требует (на основе комментариев Björn Pollex ниже).
  • Напишите метод private вместо лямбда и передайте это как обратный вызов (например, используя std::bind для удобства). Если вы хотите захватить локальные объекты в дополнение к this, вы можете использовать для этого больше std::bind на сайте вызова.

Ответ 3

Обход проблемы:

typedef  bool (A::*MemFn)(void) const;

bool A::hasGrandChild() const {
    MemFn f = &A::hasChild;
    return any_of(childs_.begin(), childs_.end(), [=](A const &a) {
            return (a.*f)();
    });
}

Ответ 4

Вы можете явно зафиксировать this и сделать его "членом лямбда", который имеет доступ к закрытым членам.

Например, рассмотрим следующий пример:

#include <iostream>
class A {
private:
    void f() { std::cout << "Private"; }
public:
    void g() { 
        [this] { 
            f(); 
            // doesn't need qualification 
        }(); 
    }
};
class B {
private:
    void f() { std::cout << "Private"; }
public:
    void g() { [] { f(); }(); } // compiler error
};
int main() {
    A a;
    a.g();
}

Ответ 5

Это невозможно, потому что лямбда не является частью класса. Это то же самое, что и делать внеклассную функцию и вызывать ее вместо создания лямбды. Конечно, у него нет доступа к закрытым членам.