О 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

Тем не менее, лямбда не имеет списка захвата, и она не может иметь ее из-за + которая вынуждает преобразование к указателю функции.

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