О lambdas, конверсиях к указателям функций и видимости частных данных
Рассмотрим следующий пример:
#include <cassert>
struct S {
auto func() { return +[](S &s) { s.x++; }; }
int get() { return x; }
private:
int x{0};
};
int main() {
S s;
s.func()(s);
assert(s.get() == 1);
}
Он компилируется как с G++, так и с clang, поэтому я склонен ожидать, что это допустимо стандартом. Тем не менее, лямбда не имеет списка захвата, и она не может иметь ее из-за +
которая вынуждает преобразование к указателю функции. Поэтому я ожидал, что ему не разрешат доступ к частным данным S
Вместо этого он ведет себя более или менее, как если бы он был определен как статическая функция-член.
Все идет нормально. Если бы я знал это раньше, я бы часто использовал этот трюк, чтобы избежать написания избыточного кода.
То, что я хотел бы знать сейчас, - это то, где в стандарте (рабочий проект в порядке) это определено, поскольку я не смог найти раздел, пулю или что-то еще, что об этом говорит.
Есть ли какое-либо ограничение для лямбда или оно работает точно так, как если бы оно было определено как статическая функция-член?
Ответы
Ответ 1
Для лямбда-выражений внутри функции-члена, согласно §8.4.5.1/2 Типы закрытия [expr.prim.lambda.closure]:
Тип закрытия объявляется в наименьшей области блока, области видимости класса или области пространства имен, которая содержит соответствующее лямбда-выражение.
Это означает, что тип лямбда-замыкания будет объявлен внутри функции-члена, то есть локального класса. И согласно §14/2 Контроль доступа членов [class.access]:
(акцент мой)
Член класса также может получить доступ ко всем именам, к которым имеет доступ класс. Локальный класс функции-члена может обращаться к тем же именам, к которым может обращаться сама функция-член.
Это означает, что для лямбда - выражение сам по себе, он может получить доступ к private
членам S
, так же как функции члена func
.
И §8.4.5.1/7 Типы закрытия [expr.prim.lambda.closure]:
(акцент мой)
Тип замыкания для не-общего лямбда-выражения без лямбда-захвата, ограничения которого (если они есть) удовлетворяются, имеет функцию преобразования для указателя на функцию с C++ языковой связью, имеющей тот же параметр и возвращаемые типы, что и функция типа закрытия вызов оператора.... Значение, возвращаемое этой функцией преобразования, является адресом функции F, которая при вызове имеет тот же эффект, что и вызов оператора вызова функции закрытия.
Это означает, что при вызове преобразованного указателя функции применяется то же правило.
Ответ 2
Тем не менее, лямбда не имеет списка захвата, и она не может иметь ее из-за +
которая вынуждает преобразование к указателю функции.
+
не приводит к преобразованию в указатель функции, но добавляет оператор преобразования к указателю на функцию для использования в качестве опции. Лямбда остается лямбдой, со всеми предоставленными ей правами доступа, то есть она может обращаться к тем же самым именам, к которым может обращаться сама функция-член.