【问题标题】:lazy instantiation for lambda expressionlambda 表达式的惰性实例化
【发布时间】:2019-10-05 06:55:46
【问题描述】:

我想在 lambda 表达式中访问 foo::func(),但此时已声明类 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(); 处输出错误“ember access into incomplete type 'foo'”。如果我添加 void 返回类型,似乎 g++ 会懒惰地实例化 lambda 表达式的主体。但是 clang++ 仍然输出相同的错误。

结果:

哪个编译器有效?

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

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

我注意到单独声明和定义方法并不真正等同于Lambda表达式方法。 lambda表达式的参数是auto&amp;,而单独声明和定义方法lambda::operation()的参数是foo&amp;

应该是模板。这是等效的代码:

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&amp; p) {... 对应于void bar::memfun(foo&amp; f)
    • 由于 SML 重载解析,参数foo&amp; f 不能为auto&amp; f
  • 内部的lambda表达式[](auto&amp; p) -&gt; void { ...对应[](auto&amp; f) -&gt; void { ...
  • auto table::operator()() const noexcept 不能与声明和定义分开,因为 SML 在定义 operator()() 之前使用返回类型。

【问题讨论】:

  • 你能把中间的文本转换成 cmets 并将代码放在一起吗?这使minimal reproducible example 中的内容更加清晰。
  • 你使用了一个不完整的结构体,它只是一个没有任何成员函数声明的声明。所以编译器此时不知道什么是func,它是结构的成员函数。那么,为什么不在 struct bar 之前定义 struct foo 呢?或者按照你第一次尝试定义 lambda 结构的方式定义结构 foo
  • @user9335240,此代码关注编译器行为。原始问题是github.com/boost-experimental/sml/issues/…。 Boost.SML 需要auto 返回类型转换表。在此问题中,struct table 由于 Boost.SML 要求而无法分离。
  • // Write explicit return type seems to instanciate lambda body lazily on g++ “instanciate” 像“template instanciate”还是“struct instanciate”?每当实例化 lambda 结构时,恕我直言。 “制作”功能发生在编译时。因此,我发现“不完整的结构”是合理的。如果g++ 恰好编译了这个,它应该与Phase 7 or Phase 8 中是否发生事情有关。我想知道在这种情况下您是否可以指望g++s 的行为。
  • @TakatoshiKondo 你的代码看起来这个函数是foo 的半纯函数(不是闭包)(在问题中,它是with_prop),所以为什么不在这里使用函数指针?? typedef void (*lambda_type) (struct foo &amp;); 然后声明一个extern const,然后在foo 声明之后将其声明为一个lambda。 wandbox.org/permlink/6EhybAMZjfMgZ2v5

标签: c++ lambda c++14 lazy-evaluation generic-lambda


【解决方案1】:

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

[temp.point](强调我的)

1 对于函数模板特化,成员函数 模板特化,或成员函数的特化,或 类模板的静态数据成员,如果特化是 隐式实例化,因为它是从另一个内部引用的 模板特化和引用它的上下文 取决于模板参数,实例化点 专业化是封闭的实例化点 专业化。 否则,这样的实例化点 特化紧跟命名空间范围声明或 指专业化的定义

8函数模板的特化,成员函数 模板,或类的成员函数或静态数据成员 模板中可能有多个实例化点 翻译单元,除了实例化点 如上所述,对于任何具有以下意义的专业化 翻译单元内的实例化,翻译结束 unit 也被认为是一个实例化点。专业化 因为一个类模板在一个类模板中最多有一个实例化点 翻译单元。任何模板的特化可能有以下几点 在多个翻译单元中实例化。 如果两个不同的点 实例化赋予模板特化不同的含义 根据单定义规则,程序是非良构的,不 需要诊断

首先这意味着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);
}

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

【讨论】:

  • 你的意思是我的第二种方法带有成员函数模板方法的函数对象格式不正确,并且意外地看起来运行良好,这是未定义的行为吗?那正确吗?我添加了问题背景。 wandbox.org/permlink/Z2gUxZOA5B2is2j7 不分隔一,wandbox.org/permlink/fzHXGIVZ4zBcorAm 分隔一。但是分开一个会导致编译错误,因为 SML 在定义之前需要 operator()() 返回类型。恐怕库设计可能有问题……
  • @TakatoshiKondo - 是的,您展示的模板和通用 lambda 版本都是格式错误的 NDR。至于 SML,我无法谈论它的设计。我不熟悉它。
  • 感谢您的澄清。我的第一种方法单独声明和定义方法格式正确吗?我想是的。
  • @TakatoshiKondo - 是的,它的格式很好。缺少模板也允许编译器强制执行它。
  • 我考虑了你的回答。我不认为我的代码格式错误的原因是违反 ODR。我认为您强调的最后一句话是针对多个翻译单元的。描述 ODR 是很自然的情况。仅在一个翻译单元中,实例化点应确定为一个点。你怎么看?
【解决方案2】:

在我看来,您正在寻找的(并且您几乎在最后两种方法中使用)是一个通用 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 中接收一个使用 func() 的泛型类型(auto,在您的上一种方法中等效于您的模板 lambda::operator())。

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

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

-- 编辑--

OP 精确

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

抱歉,同时阅读您的背景编辑,我不了解您的确切需求。

无论如何,如果关键是您需要一个接受 foo&amp; 的 lambda,我建议将其写入模板函数但在 foo 定义之后推迟其生产。

观察最终的static_assert(),验证bar 是一个接受foo&amp; 的lambda(更好:验证可转换为接受foo&amp; 并返回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 &)>, "!" );
}

【讨论】:

  • @user9335240 - 在我的 linux 平台(g++ 8.3.0 和 clang++ 7.0.1)中两者都可以使用
  • @max66,谢谢你的回答。我不能用auto&amp; 替换foo&amp;。我应该添加我的问题背景。所以我在最后一部分的问题中添加了Background
  • @TakatoshiKondo - 抱歉,我不了解您的确切需求。我在答案中添加了另一个示例,其中bar 是一个接受foo&amp; 的lambda。希望这会有所帮助。
  • @max66, wandbox.org/permlink/qX1W0cCYrsXv1phD 是我想做的。它在 g++ 上编译(我认为它是 UB 之一),但不编译 clang++。我正在寻找形成良好状态的方法。如果可以,我不想分开。
猜你喜欢
  • 1970-01-01
  • 2010-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多