ленивый экземпляр для лямбда-выражения - PullRequest
4 голосов
/ 05 октября 2019

Я хочу получить доступ к foo::func() в лямбда-выражении, но класс foo объявлен, но не определен на этом этапе. Есть ли способ для лямбда-выражения лениво?

Если я заменю лямбда-выражение эквивалентным функциональным объектом, то я могу это сделать.

Вот эквивалентный код:

Отдельный подход к объявлению и определению

struct foo; // forward declaration

struct lambda {
    void operator()(foo& f); // `lambda` only has declaration of `operator()`.
};

struct bar {
    void memfun(foo& f) {
        // Call `lambda` function object with the reference of incomplete `foo`.
        lambda()(f);
    }
};

struct foo { // Define foo
    void func() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

// Define `lambda::operator()` after definition of `foo`.
inline void lambda::operator()(foo& f) {
    f.func();
}

int main() {
    foo f;
    bar b;
    b.memfun(f);
}

Запуск демо: https://wandbox.org/permlink/12xV6655DZXZxLqF

Он может быть скомпилирован как на g ++, так и на clang ++.

Подход лямбда-выражений, который является моей целью

Я пытался исключить struct lambda.

Вот код:

struct foo; // forward declaration

struct bar {
    void memfun(foo& f) {
        // Write explicit return type seems to instanciate 
        // lambda body lazily on g++ 
        [](auto& f) -> void {
            f.func();
        }(f);
    }
};

struct foo { // definition
    void func() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};


int main() {
    foo f;
    bar b;
    b.memfun(f);
}

Смысл в явном написании типа возврата void. Если я опущу это, то компилятор и g ++, и clang ++ выдают ошибку «доступ к неполному типу 'foo'" в f.func();. Если я добавлю void возвращаемый тип, похоже, что g ++ лениво создает экземпляр тела лямбда-выражения. Однако clang ++ по-прежнему выдает ту же ошибку.

Результат:

Какой компилятор допустим?

Если clang ++ допустим, есть лилюбой способ создания экземпляра тела лямбда-выражения, лениво похожий на эквивалентный struct lambda?

объект функции с подходом шаблона функции-члена

Я заметил, что Подход отдельного объявления и определения на самом деле не эквивалентен лямбда-выражению . Параметр лямбда-выражения равен auto&, но параметр lambda::operation() в Подходе отдельного объявления и определения равен foo&.

Это должен быть шаблон. Это эквивалентный код:

struct foo; // forward declaration

struct lambda {
    template <typename T>
    void operator()(T& f) {
        f.func();
    }
};

struct bar {
    void memfun(foo& f) {
        lambda()(f);
    }
};

struct foo { // definition
    void func() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

int main() {
    foo f;
    bar b;
    b.memfun(f);
}

Демонстрация работы: https://wandbox.org/permlink/dJ1tqQE8dIMNZqgY

Не требуется отдельное снижение значения lambda::operator(). И создавать его лениво как на g ++, так и на clang ++. Я ищу путь к тому же, используя лямбда-выражение, если это возможно.

Фон (Зачем мне это нужно?)

Я использую Boost (Candidate) SML,библиотека конечного автомата, основанная на метапрограммировании.

См. https://github.com/boost-experimental/sml/issues/93#issuecomment-283630876

  • struct with_prop соответствует struct foo.
  • struct table соответствует struct bar.
  • Внешнее лямбда-выражение [](with_prop& p) {... соответствует void bar::memfun(foo& f).
    • Из-за разрешения перегрузки SML параметр foo& f не может быть auto& f.
  • Внутреннее лямбда-выражение [](auto& p) -> void { ... соответствует [](auto& f) -> void { ...
  • auto table::operator()() const noexcept нельзя разделить на объявление и определение, поскольку SML использует тип возвращаемого значения до определения operator()().

Ответы [ 2 ]

0 голосов
/ 05 октября 2019

Немного тангенциально, но люди должны быть осведомлены. Я боюсь, что код с шаблоном «работает» основан на неопределенном поведении из-за неправильной формы отчета о недоставке. Я хрупкий и мог легко сломаться.

[temp.point] (выделение мое)

1 Для специализации шаблона функции,специализация шаблона функции-члена или специализация для функции-члена или статического члена данных шаблона класса, если специализация создается неявно, так как на нее ссылаются из другой специализации шаблона, а контекст, на который она ссылается, зависит от параметра шаблонаТочка инстанцирования специализации является точкой инстанцирования вмещающей специализации. В противном случае точка создания такой специализации сразу следует за объявлением или определением области имен пространства имен, относящимся к специализации .

8 Специализация для шаблона функции,шаблон функции-члена или функция-член или статический член данных шаблона класса может иметь несколько точек экземпляров в пределах единицы перевода и в дополнение к точкам экземпляров, описанным выше, для любой такой специализации, которая имеетточка создания экземпляра в единице перевода, конец единицы перевода также считается точкой создания . Специализация для шаблона класса имеет не более одной точки создания экземпляра в единице перевода. Специализация для любого шаблона может иметь точки создания экземпляров в нескольких единицах перевода. Если две разные точки инстанцирования придают специализации шаблона разные значения в соответствии с правилом единого определения, программа плохо сформирована, диагностика не требуется .

Итак, сначала это означаетчто шаблон operator() имеет две точки создания. Один находится сразу после bar, а другой - в конце блока перевода. В первой точке реализации foo является неполной, в то время как во второй она завершается.

В этих двух точках реализации специализация шаблона имеет различное значение ! В одном случае конкретизированная специализация плохо сформирована, потому что она вызывает функцию-член неполного типа. В то время как во втором тип завершен. Как и в последнем предложении цитаты, это неправильно сформированный отчет о недоставке.

Единственный способ сделать его правильно сформированным - это немного изменить код.

struct bar {
    void memfun(foo& f);
};

struct foo { // definition
    void func() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

void bar::memfun(foo& f) {
    [](auto& f) -> void {
        f.func();
    }(f);
}

Теперьдве точки инстанцирования согласуются по своему значению, и риск носовых демонов исчез.

0 голосов
/ 05 октября 2019

Мне кажется, что то, что вы ищете (и что вы почти используете в последних двух подходах) - это общая лямбда.

Я имею в виду ...

#include <iostream>

// struct foo; // forward declaration (not needed at all)

auto bar = [](auto & f) { f.func(); };

struct foo { // definition
    void func() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

int main() {
    foo f;
    bar(f);
}

Хитрость заключается в получении в лямбде универсального типа (auto, который эквивалентен вашему шаблону lambda::operator() в вашем последнем подходе), который использует func().

Таким образом,компилятору больше не нужно в момент определения bar лямбда знать, как создается foo::func().

Обратите внимание, что и ваш второй подход основан на этом решении, просто слишком сложном.

- РЕДАКТИРОВАТЬ -

Точность OP

Я не могу заменить foo& на auto&. Я должен добавить свой фон вопроса. Поэтому я добавил фон к своему вопросу в последней части.

Извините, но, также читая ваше фоновое редактирование, я не понимаю ваших точных потребностей.

В любом случае, если дело в том, что вам нужна лямбда, которая принимает foo&Я предлагаю следующее решение, которое записывает его в шаблонную функцию, но откладывает его производство после определения foo.

Обратите внимание на окончательный static_assert(), который проверяет, что bar является лямбда-выражением, принимающим foo&(лучше: убедитесь, что он конвертируется в указатель на функцию, которая принимает foo& и возвращает void)

#include <iostream>

// struct foo; // no forward declaration needed

template <typename T>
auto baz ()
 { return [](T & f){ f.func(); }; }

struct foo { // definition
    void func() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

int main() {
    foo f;

    auto bar = baz<foo>();

    bar(f);

    static_assert( std::is_same_v<decltype(+bar), void(*)(foo &)>, "!" );
}
...