4

我想foo::func()在 lambda 表达式中访问,但此时foo已声明该类但未定义该类。有什么办法可以懒惰地进行 lambda 表达式吗?

如果我用等效的函数对象替换 lambda 表达式,那么我可以做到这一点。

这是等效的代码:

单独的声明和定义方法

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++ 上编译。

我的目标是 Lambda 表达式方法

我试图消除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++ 的编译器都会在f.func();. 如果我添加void返回类型,似乎 g++ 会懒惰地实例化 lambda 表达式的主体。但是 clang++ 仍然输出相同的错误。

结果:

哪个编译器有效?

如果 clang++ 是有效的,有没有办法像等效的一样懒惰地实例化 lambda 表达式的主体struct lambda

具有成员函数模板方法的函数对象

我注意到分离声明和定义方法并不真正等同于Lambda 表达式方法。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++ 上懒惰地实例化它。如果可能的话,我正在寻找一种使用 lambda 表达式的方法。

背景(我为什么需要这个?)

我正在使用基于元编程的状态机库 Boost (Candidate) SML。

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

  • struct with_prop对应于struct foo
  • struct table对应于struct bar
  • 外部 lambda 表达式[](with_prop& p) {...对应于void bar::memfun(foo& f).
    • 由于 SML 重载解析,参数foo& f不能是auto& f.
  • 内部 lambda 表达式[](auto& p) -> void { ...对应于[](auto& f) -> void { ...
  • auto table::operator()() const noexcept不能分开声明和定义,因为 SML 在operator()()定义之前使用返回类型。
4

2 回答 2

0

在我看来,您正在寻找的(并且您几乎在最后两种方法中使用)是一个通用的 lambda。

我是说...

#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);
}

诀窍是在 lambda 中接收auto一个lambda::operator()使用func().

这样,编译器在定义 lambda 的那一刻就不再需要bar知道如何foo::func()制作了。

请注意,您的第二种方法也基于此解决方案,只是过于复杂。

- 编辑 -

OP精确

我无法替换foo&auto&. 我应该添加我的问题背景。所以我在最后一部分为我的问题添加了背景。

对不起,但是,也阅读了你的背景编辑,我不明白你的确切需求。

无论如何,如果关键是您需要一个接受 a 的 lambda foo&,我提出以下解决方案,将其编写在模板函数中,但在foo定义后推迟其生产。

观察最终static_assert()验证bar是接受 a 的 lambda foo&(更好:验证可转换为接受 afoo&并返回的函数指针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 &)>, "!" );
}
于 2019-10-05T09:17:54.927 回答
0

有点切题,但应该让人们意识到这一点。由于 NDR 格式不正确,我担心带有“工作”模板的代码依赖于未定义的行为。我很脆弱,很容易折断。

[temp.point](强调我的)

1对于函数模板特化、成员函数模板特化或类模板的成员函数或静态数据成员的特化,如果特化是隐式实例化的,因为它是从另一个模板特化及其所在的上下文中引用的被引用取决于模板参数,特化的实例化点是封闭特化的实例化点。否则,这种特化的实例化点紧跟在引用特化的命名空间范围声明或定义之后

8函数模板、成员函数模板或类模板的成员函数或静态数据成员的特化可以在翻译单元内具有多个实例化点,并且除了上述实例化点之外,对于任何这种在翻译单元内有一个实例化点的特化,翻译单元的末端也被认为是一个实例化点。类模板的特化在翻译单元内最多有一个实例化点。任何模板的特化都可能在多个翻译单元中具有实例化点。如果根据单定义规则,两个不同的实例化点赋予模板特化不同的含义,则程序是非良构的,不需要诊断

所以首先它意味着operator()模板有两个实例化点。一个在 之后bar,另一个在翻译单元的末尾。在实例化的第一点foo是不完整的,而在第二点它是完整的。

在这两个实例化点上,模板特化具有不同的含义!一方面,实例化的特化是格式错误的,因为它调用了不完整类型的成员函数。而在第二个类型完成。就像引用的最后一句话所说,这是格式错误的 NDR。

使其格式良好的唯一方法是稍微移动代码。

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);
}

现在两个实例化点的意思一致了,鼻魔的风险就没有了。

于 2019-10-05T09:39:32.947 回答