c++ - 在可变参数模板中使用垫片的更简洁的方法?

标签 c++ c++11 variadic-templates shim

C++ 模板通常被认为是膨胀的创造者,而 Shim 的想法正是处理这个问题:使模板只是一个常规函数的薄包装。这是减少臃肿的好方法。

例如,让我们使用一个简单的 shim:

//
// Shim interface
//
struct Interface {
    virtual void print(std::ostream& out) const = 0;
}; // struct Interface

std::ostream& operator<<(std::ostream& out, Interface const& i) {
    i.print(out);
    return out;
}

template <typename T>
struct IT: public Interface {
    IT(T const& t): _t(t) {}
    virtual void print(std::ostream& out) const { out << _t; }
    T const& _t;
};

template <typename T>
IT<T> shim(T const& t) { return IT<T>(t); }

现在,我可以像这样使用它了:

void print_impl(Interface const& t);

template <typename T>
void print(T const& t) { print_impl(shim(t)); }

而且无论 print_impl 是如何实现的,print 仍然是非常轻量级的,应该被内联。简单易行。


然而,C++11 引入了可变参数 模板。然后,典型的冲动是使用 C++11 可变参数模板重新实现所有不安全的 C 可变参数,甚至维基百科也建议使用 a printf implementation。 .

不幸的是,维基百科的实现不处理位置参数:允许您在那里指定打印第三个参数的那种,等等......如果我们有一个带有这个原型(prototype)的函数,那就很容易了:

void printf_impl(char const* format, Interface const* array, size_t size);

或类似的。

现在,我们如何从原始界面桥接:

template <typename... T>
void printf(char const* format, T const&... t);

到上面的签名?

垫片的一个困难是它们依赖于绑定(bind)到 const-ref 行为来延长创建的临时包装器的生命周期,而不必动态分配内存(它们不会很便宜如果他们这样做了)。

尽管一步完成绑定(bind)+数组转换似乎很困难。特别是因为引用数组(和指向引用的指针)在语言中是不允许的。


对于那些感兴趣的人,我有一个解决方案的开始:

//
// printf (or it could be!)
//
void printf_impl(char const*, Interface const** array, size_t size) {
    for (size_t i = 0; i != size; ++i) { std::cout << *(array[i]); }
    std::cout << "\n";
}

template <typename... T>
void printf_bridge(char const* format, T const&... t) {
    Interface const* array[sizeof...(t)] = { (&t)... };
    printf_impl(format, array, sizeof...(t));
}

template <typename... T>
void printf(char const* format, T const&... t) {
    printf_bridge(format, ((Interface const&)shim(t))...);
}

但是您会注意到引入了一个补充步骤,这有点烦人。尽管如此,it appears to work .

如果有人能提出更好的实现方案,我将不胜感激。


@Potatoswatter 建议使用初始化列表,helps a bit (那里没有范围)。

void printf_impl(char const*, std::initializer_list<Interface const*> array) {
    for (Interface const* e: list) { std::cout << *e; }
    std::cout << "\n";
}

template <typename... T>
void printf_bridge(char const* format, T const&... t) {
    printf_impl(format, {(&t)...});
}

但还是没有解决中间函数的问题。

最佳答案

使其轻量级取决于消除类型参数化。您的 shim 可能会使用表达式 out << _t 实例化一些重负载的东西, 所以它可能不是一个很好的例子。

C 可变参数通过将所有内容隐式转换为 intptr_t 来处理这个问题.如果您只想复制 C printf功能,你可以用reinterpret_cast做同样的事情和一个 initializer_list .

template <typename... T>
void printf(char const* format, T const&... t) {
    printf_impl(format, { reinterpret_cast< std::intptr_t >( t ) ... } );
}

这显然不是最理想的,但垫片本身是有限的。您可以在 initializer_list 中使用多态类型做其他事情如果你愿意的话。

无论如何,这正是initializer_list是为了。它只能从花括号初始化列表构造,使其大小成为编译时常量。但大小只能作为运行时常量读回。因此,它唯一的实际用途是将仅列表长度不同的模板汇集到一个通用的可变长度实现中。

再加上 initializer_list 的生命周期语义arguments — 对象在堆栈上的连续数组中创建,并在函数调用语句结束时消亡 — 和 initializer_list看起来很像 <varargs> ! (编辑:或您的解决方案,我现在实际上已经回去阅读了:vP)

编辑:由于容器不能直接存储多态对象,而且智能指针不适用于临时参数对象,因此实现多态需要将指针指向临时对象。丑陋但合法,因为临时对象的生命周期得到保证:

template <typename... T>
void printf(char const* format, T const&... t) {
    printf_impl(format, std::initializer_list< Interface const * >
        { & static_cast< Interface const & >( shim(t) )... } );
}

关于c++ - 在可变参数模板中使用垫片的更简洁的方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10161431/

相关文章:

c++ - Operator<< 重载,endl导致segmentation fault

c++ - Linux 上 gcc 4.9.2 的奇怪行为

c++11如何将十六进制字符串转换为unicode字符串

c++ - 链接隐式转换运算符

c++ - 指向结构/类的递归成员的可变指针序列作为模板参数

c++ - 未按预期调用析构函数

C++将多个对象序列化到一个文件并反序列化有限数量的对象

c++ - 在 std::pair 上使用 lambda 理解 std::find_if()

c++ - 从可变参数模板调用仿函数

c++ - clang 与 gcc : variadic lambda captures