在可变参数模板中使用填充程序的更简洁方法?

Mat*_* M. 7 c++ shim variadic-templates c++11

C++模板通常被臃肿的创造者所吸收,而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); }
Run Code Online (Sandbox Code Playgroud)

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

void print_impl(Interface const& t);

template <typename T>
void print(T const& t) { print_impl(shim(t)); }
Run Code Online (Sandbox Code Playgroud)

无论如何print_impl实现,print仍然非常轻量级,应该内联.十分简单.


但是,C++ 11引入了可变参数模板.然后典型的冲动是重新实现所有不安全的C-variadics与C++ 11个可变参数模板,甚至维基百科提出这样一个printf实现.

不幸的是,维基百科的实现不涉及位置参数:允许你指定在那里打印第三个参数的类型等等......如果我们只有这个原型的函数,那将很容易:

void printf_impl(char const* format, Interface const* array, size_t size);
Run Code Online (Sandbox Code Playgroud)

或类似的.

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

template <typename... T>
void printf(char const* format, T const&... t);
Run Code Online (Sandbox Code Playgroud)

到上面的签名?

填充程序的一个难点是它们依赖于对const-ref行为的绑定来延长创建的临时包装器的生命周期,而不必动态分配内存(如果他们这样做,它们就不便宜).

虽然在一步中获得绑定+数组转换似乎很困难.特别是因为语言中不允许引用数组(和引用指针).


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

//
// 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))...);
}
Run Code Online (Sandbox Code Playgroud)

但是你会注意到一个补充步骤的介绍,这有点烦人.仍然有效.

如果有人有更好的实施建议,我将非常感激.


@Potatoswatter建议使用初始化列表,这有点帮助(没有范围 - 那里).

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)...});
}
Run Code Online (Sandbox Code Playgroud)

但仍然没有解决中间功能问题.

Pot*_*ter 3

使其轻量级取决于消除类型参数化。您的垫片可能会使用表达式实例化一些重型内容out << _t,因此它可能不是一个很好的例子。

\n\n

C varargs 通过将所有内容隐式转换为 来处理该问题intptr_t。如果您只想复制 Cprintf功能,则可以使用reinterpret_castinitializer_list.

\n\n
template <typename... T>\nvoid printf(char const* format, T const&... t) {\n    printf_impl(format, { reinterpret_cast< std::intptr_t >( t ) ... } );\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

\n\n

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

\n\n

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

\n\n
template <typename... T>\nvoid printf(char const* format, T const&... t) {\n    printf_impl(format, std::initializer_list< Interface const * >\n        { & static_cast< Interface const & >( shim(t) )... } );\n}\n
Run Code Online (Sandbox Code Playgroud)\n