我正在使用类型删除来获取任何 class full
带有成员函数 void work(char&)
带有 class erased
的类型删除句柄.
// erase.hxx
#pragma once
#include <memory>
struct erased
{
private:
using fn_t = void(*)(void*, char&);
void* self;
fn_t fn;
public:
template<typename F>
explicit
erased(F& full) noexcept
: self(std::addressof(full)),
fn([](void* self, char& c) { static_cast<F*>(self)->work(c); })
{}
void work(char& c) { fn(self, c); }
};
// full.hxx
#pragma once
struct full
{
void work(char&);
};
// full.cxx
#include "full.hxx"
#include <cstdio>
// Implemented here to prevent inlining.
void full::work(char&) { puts("Working hard!"); }
// main.cxx
#include "erased.hxx"
#include "full.hxx"
template erased::erased(full&);
int main()
{
char c;
auto x = full{};
auto ex = erased{x};
ex.work(c);
}
在查看生成的程序集(GCC 10.2.0 和 Clang 11.1.0 at -O3)时,我的问题出现了:0000000000001190 <erased::erased<full>(full&)>:
1190: 48 89 37 mov QWORD PTR [rdi],rsi
1193: 48 8d 05 06 00 00 00 lea rax,[rip+0x6] # 11a0 <erased::erased<full>(full&)::{lambda(void*, char&)#1}::__invoke(void*, char&)>
119a: 48 89 47 08 mov QWORD PTR [rdi+0x8],rax
119e: c3 ret
119f: 90 nop
00000000000011a0 <erased::erased<full>(full&)::{lambda(void*, char&)#1}::__invoke(void*, char&)>:
11a0: e9 0b 00 00 00 jmp 11b0 <full::work(char&)>
11a5: 66 2e 0f 1f 84 00 00 cs nop WORD PTR [rax+rax*1+0x0]
11ac: 00 00 00
11af: 90 nop
字段erased::fn
指向 erased::construct
中创建的 lambda ,并且这个 lambda 的主体除了立即将控制权让给 full::work
之外什么都不做。 .由于 lambda 和
full::work
似乎是二进制兼容的 我希望编译器取消 lambda 并直接存储 full::work
的地址在 erased::fn
,消除不必要的间接。所以我的问题是:
编辑
我更改了
struct erase
的实现让任何时候都无法调用erased::fn
任何不是指向同一类型的指针F
static_cast<F*>
中使用的在 lambda 。即使没有,我怀疑假设
void* self
传递给 lambda 始终是指向 F
的指针因为F::work
在其上调用 this :Calling a function through an expression whose function type is different from the function type of the called function's definition results in undefined behavior.
此外,我通过将函数类型更改为
using fn_t = void(*)(void*,char&)
使示例更加真实。 : 除了 this 指针之外,它现在还需要一个参数。这是为了说明,即使在上面的示例中,我所询问的优化应该是可能的,但当
F::work
有签名void work(char)
:c
的拷贝必须制作:lambda 的主体将不再仅由 jmp
组成.我更喜欢两种情况都有效且编译器决定优化是否可行的解决方案。
否则,我知道我可以强制参数类型与此完全匹配:
template<typename M, typename... Args>
struct method_with_args : std::false_type {};
template<typename F, typename R, typename... Args>
struct method_with_args<R(F::*)(Args...), Args...>
: std::true_type{};
最佳答案
(也许我不在这里,但是)“lambda 和 full::work 似乎是二进制兼容的”并不是真的。成员函数有一个隐藏的第一个参数:指向成员对象的指针(此处为:F* this
)。
指向 F::work
的函数指针因此将是 void(F::*)(void)
.这与 void(*)(void*)
不同。 ,因此不能这样替换。
也许 this FAQ on isocpp将提供一些见解。
关于c++ - 使编译器在类型删除中使用 lambda 优化函数调用间接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67574363/