相互依赖的局部类(或相互递归的lambdas)
mutually dependent local classes (or mutually recursive lambdas)
我经常在方法内部创建本地助手类,只要这样的类在本地有用,但在方法外部无关。我刚刚遇到一个案例,我想要两个相互依赖的本地类。
其想法是具有以下模式:
void SomeClass::someMethod()
{
struct A
{
B * b;
void foo() { if(b) b->bar(); };
};
struct B
{
A * a;
void bar() { if(a) a->foo(); }
};
}
但是由于A
需要B
,所以它没有编译。正向声明B
有助于使行B * b;
编译,但方法A::foo()
仍然需要B
的完整声明,编译器会抱怨。
我看到了两个解决方案:
在
SomeClass::someMethod()
之前的SomeClass.cpp中声明和定义类。我觉得这并不优雅,因为类不仅不是SomeClass::someMethod()
的本地类,甚至不是SomeClass
的本地类。声明SomeClass.h中的类嵌套在
SomeClass
中,并在SomeClass.cpp中定义它们。我也不喜欢这个解决方案,因为不仅这些类不是SomeClass::someMethod()
的本地类,而且它确实污染了SomeClass.sh,原因无非是语言的限制。
因此有两个问题:SomeClass::someMethod()
本地的类是否可能?如果没有,你会看到更优雅的解决方案吗?
实现一个虚拟的a,供B使用,然后是真实的a。
struct virtA
{
virtual void foo() = 0 ;
} ;
struct B
{
virtA * a ;
void bar() { if ( a) { a->foo() ; } }
} ;
struct A : public virtA
{
B * b ;
void bar() { if ( b) { b-> bar() ; } }
} ;
因为答案似乎是:"不可能有干净的相互依赖的本地类",所以我最喜欢的解决方法是将逻辑移到结构本身之外。正如雷米亚贝尔在问题评论中所建议的那样,这可以通过创建第三个类来完成,但我最喜欢的方法是创建相互递归的lambda函数,因为它可以捕获变量(因此在实际使用中使我的生活更轻松)。所以它看起来像:
#include <functional>
#include <iostream>
int main()
{
struct B;
struct A { B * b; };
struct B { A * a; };
std::function< void(A *) > foo;
std::function< void(B *) > bar;
foo = [&] (A * a)
{
std::cout << "calling foo" << std::endl;
if(a->b) { bar(a->b); }
};
bar = [&] (B * b)
{
std::cout << "calling bar" << std::endl;
if(b->a) { foo(b->a); }
};
A a = {0};
B b = {&a};
foo(&a);
bar(&b);
return 0;
}
编译和打印:
calling foo
calling bar
calling foo
注意,必须手动指定lambda的类型,因为类型推理不能很好地使用递归lambda。
我过去也认为这是不可能的,但有一种挥之不去的想法,认为Y组合子可以得到很好的利用,现在是2021年了,constexpr有助于在一种可能比Haskell更好的语言中创建一种语言。此外,它是编写函数式语言编译器时经常发生的一类问题的基础。。。
为了找到最佳解决方案,需要对如何解决这种鸡/蛋的情况进行一些横向思考:
首先,我们不能在本地类中使用auto或模板,那么我们可以做些什么吗?我们可以定义作为函数的lambda——它们被发送的类型参数,忽略任何实例数据,允许它们是constexpr。这意味着我们可以定义一个函数来创建对象B,给定a的类型。。。但我们还没有完成,我们还有一个数据段需要处理。类创建函数需要知道这一点,否则我们必须显式地使用a::i从a段获取数据。因此,就像在汇编代码中一样,我们必须将数据和代码段分开,由于数据的类型更简单,我们将其放在依赖项列表的第一位,并将其作为虚拟基类派生两次,这是我为虚拟基类找到的为数不多(只有?)的有效用途之一(这种模式最好避免,但在这里,不可避免?)。
然后我们基本上处理Y组合子的第一次迭代。在A::foo的上下文中,我们使用类扩展程序crB来创建B对象的精确类型,并将其强制转换为该类型的指针";呼叫";
这就是它变得有趣的地方。如果代码的类型足够好,编译器可以推断出我们打算进行互尾递归,并取消对jmp的调用,jmp是一个对目标正确的函数代码至关重要的过程。
使用std::函数,它是使用编译器不友好的虚拟函数调用实现的,会破坏这种优化的任何机会,所以这种方法可能更友好,因为编译器可以访问所有涉及的类型,而不需要任何间接操作?
#include <iostream>
int main(int argc, char* argv[])
{
struct Data
{
int i = 563;
};
constexpr auto crB = [](auto par)
{
using T = decltype(par);
struct B : public T, virtual public Data
{
void foo() {
std::cerr << "B: " << i << "n";
i = (3 * i) + 1;
T::foo();
}
};
return B();
};
struct A : virtual public Data
{
void foo()
{
std::cerr << "A: " << i << "n";
i >>= 1;
if (i == 1)
return;
using ForwardT = decltype(crB(A()));
(i&1)? static_cast<ForwardT *>(this)->foo() : foo();
};
};
auto binst = crB(A());
binst.foo();
return 0;
}
那么,这种高级汇编语言的编译目的是什么呢?
.LC0:
.string "B: "
.LC1:
.string "n"
.LC2:
.string "A: "
main::{lambda(auto:1)#1}::operator()<main::A>(main::A) const::B::foo():
push rbx
mov rbx, rdi
.L3:
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cerr
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov rdi, rax
mov rax, QWORD PTR [rbx]
mov rax, QWORD PTR [rax-24]
mov esi, DWORD PTR [rbx+rax]
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov esi, OFFSET FLAT:.LC1
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov rax, QWORD PTR [rbx]
mov rdx, QWORD PTR [rax-24]
add rdx, rbx
imul eax, DWORD PTR [rdx], 3
inc eax
mov DWORD PTR [rdx], eax
.L4:
mov esi, OFFSET FLAT:.LC2
mov edi, OFFSET FLAT:_ZSt4cerr
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov rdi, rax
mov rax, QWORD PTR [rbx]
mov rax, QWORD PTR [rax-24]
mov esi, DWORD PTR [rbx+rax]
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov esi, OFFSET FLAT:.LC1
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov rax, QWORD PTR [rbx]
mov rdx, QWORD PTR [rax-24]
add rdx, rbx
mov eax, DWORD PTR [rdx]
sar eax
mov DWORD PTR [rdx], eax
cmp eax, 1
je .L1
test al, 1
je .L4
jmp .L3
.L1:
pop rbx
ret
main:
sub rsp, 24
mov rdi, rsp
mov QWORD PTR [rsp], OFFSET FLAT:vtable for main::{lambda(auto:1)#1}::operator()<main::A>(main::A) const::B+24
mov QWORD PTR [rsp+8], 563
call main::{lambda(auto:1)#1}::operator()<main::A>(main::A) const::B::foo()
xor eax, eax
add rsp, 24
ret
_GLOBAL__sub_I_main:
push rax
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
pop rcx
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
jmp __cxa_atexit
typeinfo for main::Data:
.quad vtable for __cxxabiv1::__class_type_info+16
.quad typeinfo name for main::Data
typeinfo name for main::Data:
.string "*Z4mainE4Data"
typeinfo for main::A:
.quad vtable for __cxxabiv1::__vmi_class_type_info+16
.quad typeinfo name for main::A
.long 0
.long 1
.quad typeinfo for main::Data
.quad -6141
typeinfo name for main::A:
.string "*Z4mainE1A"
typeinfo for main::{lambda(auto:1)#1}::operator()<main::A>(main::A) const::B:
.quad vtable for __cxxabiv1::__vmi_class_type_info+16
.quad typeinfo name for main::{lambda(auto:1)#1}::operator()<main::A>(main::A) const::B
.long 2
.long 2
.quad typeinfo for main::A
.quad 2
.quad typeinfo for main::Data
.quad -6141
typeinfo name for main::{lambda(auto:1)#1}::operator()<main::A>(main::A) const::B:
.string "*ZZ4mainENKUlT_E_clIZ4mainE1AEEDaS_E1B"
vtable for main::{lambda(auto:1)#1}::operator()<main::A>(main::A) const::B:
.quad 8
.quad 0
.quad typeinfo for main::{lambda(auto:1)#1}::operator()<main::A>(main::A) const::B
.quad 0
.quad typeinfo for main::{lambda(auto:1)#1}::operator()<main::A>(main::A) const::B
所有的尾部递归调用都被取消了。翻译基本上是完美的。
- 通过递归进行因子分解
- 递归函数计算序列中的平方和(并输出过程)
- 使用递归的数组的最小值.这是怎么回事
- 递归列出所有目录中的C++与Python与Ruby的性能
- 递归计数给定目录的文件和所有目录
- 如何在BST的这个简单递归实现中消除警告
- C++:正在检查LinkedList中的回文-递归方法-错误
- 递归模板化函数不能分配给具有常量限定类型"const tt &"的变量"state"
- 递归无序映射
- TSP递归解的迭代形式
- 如何在Elixir中调用递归函数并行
- 返回递归调用和仅递归调用的区别
- 数组元素打印的递归方法
- 使用递归时获取变量的奇怪值
- 如何在C++中递归地按相反顺序打印集合
- 到连接组件算法的问题(递归)
- 如何使用递归打印修改后的星号三角形图案
- 使用递归模板动态分配的多维数组
- 递归lambdas和本地参考
- 相互依赖的局部类(或相互递归的lambdas)