我过去也认为这也是不可能的,但有人认为 Y 组合器可以很好地利用,现在是 2021 年,而 constexpr 有助于在可能比 Haskell 更好的语言中创建一种语言。此外,它是编写函数式语言编译器时经常出现的一类问题的基础......
要找到最佳解决方案,需要横向思考如何破解这种先有鸡还是先有蛋的局面:
首先我们不能在本地类中使用自动或模板,那么我们可以做些什么呢?我们可以定义作为函数类型参数的 lambda,忽略任何实例数据,允许它们是 constexpr。这意味着我们可以定义一个函数来创建对象 B,给定 A 的类型……但是我们还没有完成,我们还有一个数据段要处理。类创建函数需要知道这一点,否则我们必须显式地使用 A::i 从 A 段获取数据。因此,就像在汇编代码中一样,我们必须将数据和代码段分开,并且由于数据具有更简单的类型,我们将其放在依赖项列表中的第一位,并将其作为虚拟基类派生两次,这是少数几个之一(仅?)我为虚拟基类找到的有效用途(最好避免的模式,但在这里,不可避免?)。
然后我们基本上使用 Y 组合器的第一次迭代。在 A::foo 的上下文中,我们使用类扩展器 crB 创建 B 对象的精确类型,并将 this 转换为该类型的指针以“调用”它。
这就是有趣的地方。如果代码的类型足够好,编译器可以推断出我们打算进行相互尾递归,并省略对 jmp 的调用,该过程对于正确定位的功能代码至关重要。
使用 std::function,它是使用编译器不友好的虚函数调用实现的,会破坏这种优化的任何机会,所以也许这种方法更友好,因为编译器可以访问所有涉及的类型,而无需任何间接?
#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
所有尾递归调用都已被省略。翻译基本完美。