Я пытаюсь объявить класс «Lambdas», который предоставил бы лямбды (и информацию о их типах) другому классу «Test». Lambdas также содержит ссылку «this» на конкретный экземпляр Test для доступа к открытым членам Test внутри lambdas.
Я делаю это, чтобы определить лямбды один раз, а затем выводить типы в другом месте через decltype ().
Но я получаю сообщение об ошибке: Доступ участника к неполному типу:
template <typename T>
struct LambdasInstances {
T * self;
explicit LambdasInstances(T * p) : self(p) {} // CAPTURE Test "this"
auto genLambda1() {
return [=](int x){
self->testVar; // ERROR: Member access to incomplete type
};
}
};
class Test3 {
public:
LambdasInstances<Test3> instances;
int testVar;
Test3() : instances(this) {}
decltype(instances.genLambda1()) varLambda = instances.genLambda1();
void useLambda() { varLambda(123); }
};
НО, если бы я сделал genLambda () внешне определенным, то я бы столкнулся с другой проблемой — ОШИБКА: genLambda () с выведенным типом не может быть использован до того, как он будет определен !:
template <typename T>
struct LambdasInstances {
T * self;
explicit LambdasInstances(T * p) : self(p) {}
auto genLambda1(); // would be defined after Test3 declaration
};class Test3 {
public:
int testVar;
LambdasInstances<Test3> instances;
Test3() : instances(this) {}
decltype(instances.genLambda1()) varLambda = instances.genLambda1();
};
// IF WE DEFINE AFTER :: ^ genLambda() with deduced type cannot be used before its defined!
template< typename T>
auto LambdasInstances<T>::genLambda1() {
return [=](int x){
self->testVar;
};
}
Компилятору может потребоваться определение всего доступного типа, чтобы иметь возможность узнать смещение членов (например, в выражении self->testVar
компилятор должен знать смещение testVar
), но он не сможет узнать смещение конкретного члена, пока не получит полное определение, потому что компилятор должен знать выравнивание вашей структуры / класса (я бы даже предположил, что может быть задействована некоторая не прямая логика) при расчете заполнения между членами), который следует после знания всех членов, см. этот, это полностью зависит от компилятора и платформы.
Итак, вернемся к вашей проблеме. Вы говорите компилятору определить Test3
с genLambda1
как член, это лямбда, которая должна знать смещение члена testVar
, Кажется легко, правда? Но смещение testVar
зависит от определения целого Test3
(см. параграф выше) — здесь мы находимся в цикле.
Вы скажете: «Эй, тупой компилятор, я даю только указатель на лямбду, а не копию по значению, где вы должны знать весь размер` Test3, почему вы не позволили бы мне это сделать? «. Вполне законный вопрос, потому что теоретически компилятор может разрешить смещение позже, но кажется, что компиляторы недостаточно умны. И стандарт говорит:
Составной оператор лямбда-выражения возвращает тело функции (8.4) оператора вызова функции …
Это в основном говорит о том, что лямбда-тело — это тело функции, но в теле функции у вас не может быть неполных типов, верно? Лямбды относительно новы для C ++, и не все угловые случаи разработаны, поэтому будем надеяться, что в будущем это будет решено, конечно, компиляторы будут более сложными, чем стандартные.
Для вашей проблемы я вижу следующее решение:
template <typename T>
struct LambdasInstances {
explicit LambdasInstances(T* p) : _lambda([=](int x) { return p->testVar; }) {}
auto genLambda1() { return _lambda; }
private:
std::function<void(int)> _lambda;
};
class Test3 {
public:
int testVar;
LambdasInstances<Test3> instances;
Test3() : instances(this) {}
decltype(instances.genLambda1()) varLambda = instances.genLambda1();
};
int main() {
Test3 test3;
Test3* test3_ptr;
LambdasInstances<Test3> instances(&test3);
auto lambda = [=](int x) { return test3_ptr->testVar; };
std::function<void(int)> functor = lambda;
cerr << sizeof(Test3) << endl;
cerr << sizeof(LambdasInstances<Test3>) << endl;
cerr << sizeof(lambda) << endl;
cerr << sizeof(functor) << endl;
return 0;
}
Разница в том, что std::function
дает вам уровень абстракции, который защищает тип LambdasInstances::genLambda1
из определения Test3
, К сожалению, как вы увидите из main
Вывод функции занимает больше памяти, чем лямбда. Если это не удовлетворяет вашим потребностям, я рекомендую пересмотреть дизайн и, возможно, вы найдете что-то в старых добрых техниках до эпохи лямбд.
Других решений пока нет …