c++ - 使编译器在类型删除中使用 lambda 优化函数调用间接

标签 c++ gcc clang type-erasure

我正在使用类型删除来获取任何 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/

    相关文章:

    c++ - 如何在 C++ 中制作 bmp header ?

    c++ - 抑制从类型 'A' 到类型 'B' 的 g++ 警告强制转换消除常量

    gcc - 从 GCC 切换到 Clang 的常见问题是什么?

    c++ - 为什么尽管使用了 -isystem,但 clang 在我的标题上报告了警告,而 gcc 没有报告?

    c++ - 如何在构建时将 JSON 文件/对象包含在 C++ 中

    c++ - clang 的 c++11 支持可靠吗?

    c++ - 什么是 -D 编译器标志 C++(clang、GNU、MSVC)

    ios - Swift 和 Objective-c 框架公开其内部结构

    C++——不同类型对象的数组

    c++ - 使用 boost::wave 时的运行时错误消息