Почему невозможно использовать частный метод в лямбда?
Наличие класса, подобного этому:
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;
}
Кажется, что существует проблема с полностью квалифицированными именами как это не работает, но это работает.
Ответы
Ответ 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
Это невозможно, потому что лямбда не является частью класса. Это то же самое, что и делать внеклассную функцию и вызывать ее вместо создания лямбды. Конечно, у него нет доступа к закрытым членам.