c++ - 使编译器在类型擦除中使用 lambda 优化函数调用间接
问题描述
我正在使用类型擦除来为任何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
指向在 中创建的 lambda erased::construct
,并且此 lambda 的主体除了立即将控制权交给 之外什么都不做full::work
。
由于 lambda 并且full::work
看起来是二进制兼容的,我希望编译器取消 lambda 并直接存储full::work
in的地址erased::fn
,从而消除不必要的间接。
所以我的问题是:
- 为什么编译器没有这样做?更重要的是,
- 我怎样才能告诉编译器这样做?
编辑
我更改了 的实现,以使任何不是指向lambda中使用的相同类型的指针的东西都无法struct erase
调用。erased::fn
F
static_cast<F*>
即使没有这个,我怀疑也可以假设void* self
传递给 lambda 始终是指向的指针,F
因为F::work
在其上调用了this:
通过函数类型与被调用函数定义的函数类型不同的表达式调用函数会导致未定义的行为。
此外,我通过将函数类型更改为 使示例更加真实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*)
,因此不能这样替换。
也许这个关于 isocpp 的常见问题解答会提供一些见解。
推荐阅读
- python - 使用 Numpy Convolve 时幅度正在变化
- python - 在 sympy 或替代方案中导出点云方程
- batch-file - 仅移动第一个文件
- python-3.x - 尝试读取 proc.stdout.readline 中的所有行时陷入无限循环
- pine-script - 使用满足条件的数据计算均值和标准差
- scala - 如何使用 Phantom dsl 创建键空间和插入数据
- ksqldb - 与先前值的差异。kslqDB
- java - 采取每n个。使用流的对象数组中的元素
- wso2esb - WSO2 微集成代理服务 url
- android - 带有 RecyclerView 的片段上的罕见错误