【问题标题】:bridging templates with runtime arguments使用运行时参数桥接模板
【发布时间】:2026-02-22 00:50:01
【问题描述】:

我正在处理一个广泛使用模板的第 3 方 C++ 库。这使得创建一个 C API 以在我的框架中使用它变得很困难。

抽象问题,假设库提供了函数:

template <int i> void foo();
template <int i> void zoo(int v);

我想用函数头创建一个 C API:

extern "C" void c_foo(int i);
extern "C" void c_zoo(int i, int v);

一个明显的实现可能是:

void c_foo(int i)
{
     switch(i) {
         case 1: foo<1>(); break;
         case 2: foo<2>(); break;
         case 3: foo<3>(); break;
         default: break;
     };
};

void zoo(int)也做同样的事情。

如果i 的可能值范围很小,则此方法可以正常工作。如果我想处理 [1,100] 中i 的所有可能值,那么以这种方式编写代码会变得非常难看,因为有很多重复。

有没有更紧凑的方法来做到这一点,即编写更少的代码行?也许使用递归预处理器宏?

【问题讨论】:

  • “也许使用递归预处理器宏?” 有许多库提供帮助宏来执行此操作。例如。 boost 预处理器库。

标签: c++ c c++11 templates c-preprocessor


【解决方案1】:

我想问题是这个模板编号是多少?它是像内部缓冲区大小,还是更像命令字节? 人们假设一般的 C++ 程序只会生成少数几个参数值。

也许最糟糕的方法是枚举一些典型的缓冲区大小或命令名称,并仅实例化那些,因此如果 c 代码使用您的枚举作为参数,它将起作用,否则它(可怕地失败)断言。

如果有理由添加新条目会很烦人,但您可以为此编写脚本。

另一种方法可能是生成损坏的命名存根,即生成 foo__1() foo__2() 等,可能使用 boost 扩展预处理器来提供帮助。然后,C 程序员再次被自动限制在您在编译时发布的方法和范围内。

【讨论】:

    【解决方案2】:

    您可以在内部使用模板来生成必要的代码。

    一种方法是生成一个包含 100 个函数指针的调度表,然后在运行时对其进行索引。 c_foo 将生成索引的编译时序列并调用助手:

    extern "C" void c_foo(int i) {    
        c_foo_impl(std::make_integer_sequence<int,100>{}, i);
    }
    

    这个助手将生成调度表并执行调用:

    template <int... Is>
    void c_foo_impl (std::integer_sequence<int,Is...>, int i) {
        constexpr std::array<void(*)(), sizeof...(Is)> dispatch = { &foo<Is>... };
    
        //assert or some other error handling for i > sizeof...(Is)
        dispatch[i]();
    }
    

    那么你可以对zoo做同样的事情:

    extern "C" void c_zoo(int i, int v) {   
        c_zoo_impl(std::make_integer_sequence<int,100>{}, i, v);
    }
    
    template <int... Is>
    void c_zoo_impl (std::integer_sequence<int,Is...>, int i, int v) {
        constexpr std::array<void(*)(int), sizeof...(Is)> dispatch = { &zoo<Is>... };
    
        //assert or some other error handling for i > sizeof...(Is)
        dispatch[i](v);
    }
    

    如果你发现在一些地方需要这个,你可以抽象出一些细节,或者使用像Petra这样的库,它提供了一个switch_table来进行这种映射。

    Live demo

    【讨论】:

    • 我喜欢这个想法,但我想知道调度表是否在每次调用时都重新生成。我想我会尝试一些细微的变化,将指针向量嵌入到结构中,这样我就可以创建该结构的全局实例并将其用于调度。
    • 您可以制作dispatch constexpr 以避免再生。
    • VS2015 生成的汇编程序表明,即使使用constexpr,数组也会在每次调用时重新初始化。使用static 即可解决问题。
    • 刚刚在这里发布了一个相关问题:*.com/questions/47835596/…