lambda 表达式的惰性实例化
lazy instantiation for lambda expression
我想在 lambda 表达式中访问 foo::func()
,但此时 class 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);
}
重点是明确地写 return 类型 void
。
如果我省略这个,那么编译器 g++ 和 clang++ 都会在 f.func();
处输出错误 "ember access into incomplete type 'foo'"。如果我添加 void
return 类型,g++ 似乎懒惰地实例化了 lambda 表达式的主体。但是 clang++ 仍然输出相同的错误。
结果:
- 在 g++ 9.2.0 上成功
- clang++ 9.0.0 出错
哪个编译器有效?
如果 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(候选)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()()
之前使用 return 类型。
在我看来,您正在寻找的(并且您几乎在最后两种方法中使用的)是一个通用的 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()
中等同于你最后的方法)。 =27=]
这样,在 bar
lambda 定义时,编译器不再需要知道 foo::func()
是如何生成的。
请注意,您的第二种方法也是基于此解决方案,只是过于复杂了。
-- 编辑 --
OP 精确
I cannot replace foo&
with auto&
. I should add my question background. So I added Background to my question at the last part.
抱歉,我也看了你的背景编辑,我不明白你的确切需求。
无论如何,如果关键是您需要一个接受 foo&
的 lambda,我建议将其写入模板函数中的以下解决方案,但在 foo
定义之后推迟它的生产。
观察最终的 static_assert()
,验证 bar
是接受 foo&
的 lambda(更好:验证它可转换为接受 [=17= 的函数指针] 和 return 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 &)>, "!" );
}
有点离题,但应该让人们知道。由于格式不正确的 NDR,带有模板 "working" 的代码恐怕依赖于未定义的行为。我很脆弱,很容易折断。
[temp.point] (emphasis mine)
1 For a function template specialization, a member function
template specialization, or a specialization for a member function or
static data member of a class template, if the specialization is
implicitly instantiated because it is referenced from within another
template specialization and the context from which it is referenced
depends on a template parameter, the point of instantiation of the
specialization is the point of instantiation of the enclosing
specialization. Otherwise, the point of instantiation for such a
specialization immediately follows the namespace scope declaration or
definition that refers to the specialization.
8 A specialization for a function template, a member function
template, or of a member function or static data member of a class
template may have multiple points of instantiations within a
translation unit, and in addition to the points of instantiation
described above, for any such specialization that has a point of
instantiation within the translation unit, the end of the translation
unit is also considered a point of instantiation. A specialization
for a class template has at most one point of instantiation within a
translation unit. A specialization for any template may have points of
instantiation in multiple translation units. If two different points
of instantiation give a template specialization different meanings
according to the one-definition rule, the program is ill-formed, no
diagnostic required.
所以首先说明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);
}
现在实例化的两个点意义一致,鼻魔的风险就没有了。
我想在 lambda 表达式中访问 foo::func()
,但此时 class 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);
}
重点是明确地写 return 类型 void
。
如果我省略这个,那么编译器 g++ 和 clang++ 都会在 f.func();
处输出错误 "ember access into incomplete type 'foo'"。如果我添加 void
return 类型,g++ 似乎懒惰地实例化了 lambda 表达式的主体。但是 clang++ 仍然输出相同的错误。
结果:
- 在 g++ 9.2.0 上成功
- clang++ 9.0.0 出错
哪个编译器有效?
如果 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(候选)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
。
- 由于 SML 重载解析,参数
- 内部lambda表达式
[](auto& p) -> void { ...
对应[](auto& f) -> void { ...
auto table::operator()() const noexcept
无法分离声明和定义,因为 SML 在定义operator()()
之前使用 return 类型。
在我看来,您正在寻找的(并且您几乎在最后两种方法中使用的)是一个通用的 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()
中等同于你最后的方法)。 =27=]
这样,在 bar
lambda 定义时,编译器不再需要知道 foo::func()
是如何生成的。
请注意,您的第二种方法也是基于此解决方案,只是过于复杂了。
-- 编辑 --
OP 精确
I cannot replace
foo&
withauto&
. I should add my question background. So I added Background to my question at the last part.
抱歉,我也看了你的背景编辑,我不明白你的确切需求。
无论如何,如果关键是您需要一个接受 foo&
的 lambda,我建议将其写入模板函数中的以下解决方案,但在 foo
定义之后推迟它的生产。
观察最终的 static_assert()
,验证 bar
是接受 foo&
的 lambda(更好:验证它可转换为接受 [=17= 的函数指针] 和 return 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 &)>, "!" );
}
有点离题,但应该让人们知道。由于格式不正确的 NDR,带有模板 "working" 的代码恐怕依赖于未定义的行为。我很脆弱,很容易折断。
[temp.point] (emphasis mine)
1 For a function template specialization, a member function template specialization, or a specialization for a member function or static data member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization and the context from which it is referenced depends on a template parameter, the point of instantiation of the specialization is the point of instantiation of the enclosing specialization. Otherwise, the point of instantiation for such a specialization immediately follows the namespace scope declaration or definition that refers to the specialization.
8 A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. A specialization for a class template has at most one point of instantiation within a translation unit. A specialization for any template may have points of instantiation in multiple translation units. If two different points of instantiation give a template specialization different meanings according to the one-definition rule, the program is ill-formed, no diagnostic required.
所以首先说明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);
}
现在实例化的两个点意义一致,鼻魔的风险就没有了。