在variadic模板中使用垫片更简洁的方法?

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); }

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

void print_impl(Interface const& t);

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

print_impl实现print_implprint仍然非常轻便,应该内联。 十分简单。


然而C ++ 11引入了可变参数模板。 那么典型的愿望就是用C ++ 11 variadic模板重新实现所有不安全的C-variadics,甚至维基百科用printf实现也是如此。

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

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

或类似的。

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

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

到上面的签名?

垫片的一个难点是它们依赖于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))...);
}

不过你会注意到引入补充步骤,这有点烦人。 尽管如此,它似乎工作。

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


@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)...});
}

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


使它轻量化取决于消除类型参数化。 你的垫子可能会用out << _t这个表达out << _t表达一些重要的东西,所以它可能不是一个很好的例子。

C varargs通过将所有内容隐式转换为intptr_t来处理该问题。 如果您只想复制C printf功能,则可以使用reinterpret_castinitializer_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的意义所在。 它只能从一个braced-init-list构建,使其大小成为编译时常量。 但大小只能作为运行时常量读回。 所以它唯一的实际用途是将不同列表长度的模板汇集到一个通用的可变长度实现中。

再加上initializer_list参数的生命周期语义 - 当函数调用语句结束时,对象被创建在堆栈上的连续数组中,并且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) )... } );
}

如果你可以使用同类(内存大小和对齐方式)类型,请看下面的内容:

// thin template layer over regular class/methods
template< typename T, typename... Contracts>
inline void Container::bindSingleAs(){

isMultiBase< T, Contracts...>(); //compile time test

    priv::TypeInfoP         types[ sizeof...( Contracts)]
                            { &typeid( Contracts)... };

    priv::SharedUpcastSignature upcasts[ sizeof...( Contracts)]
                            { &priv::shared_upcast< T, Contracts>... };

    // dispatch over non-template method.
    container->bindSingleAs( &typeid(T), types, upcasts, sizeof...(   Contracts));
}

现在经过评论编辑后,我认为有两个相互冲突的必要条件。

  • 需要一个数组参数
  • 不需要复制开销
  • 如果printf_impl函数需要一个数组作为参数,那么这意味着数组元素应该在内存中具有相同的位置(这意味着如果1个元素是64个字节,即使它们是1个字节,也会强制所有其他元素为64个字节对齐..) 因此一个副本是必要的 ,或者至少复制到一个指向固定位置的指针,所以它绝对不可能做OP所需要的。

    我们仍然可以建立该阵列,但我们受到限制

  • 我们根本不想复制,那么我们应该静态声明数组的类型,这迫使我们构建第三种类型。

    auto Array = MakeArray( /* values*/);

    printf( Array);

  • 我们接受复制,所以我们在函数内部构建数组,因为值不知道,我们可以隐藏数组,但我们必须将参数复制到固定的内存位置,但是我们仍然将数组隐藏在内部。

  • 堆分配,允许以非常紧凑的数组传递参数,但参数必须驻留在其他地方,堆分配可能代价高昂。

  • 第一种解决方案是通过创建一个静态类型数组来处理编码过程中的额外复杂性(可以对所有元素进行排序),但这不是最理想的,因为增加了对象的大小可能会影响性能(如果数组甚至在函数调用之后仍然存在)

    第二种解决方案隐藏了模板界面的复杂性,但它无法避免将值临时复制到与第一个解决方案相同的阵列的性能成本。

    所以,这是不可能的,对不起。 另一个答案属于2号和3号之间。所有其他可能的答案将在3个类别中的一个之内。

    链接地址: http://www.djcxy.com/p/10655.html

    上一篇: More succinct way to use shims in variadic templates?

    下一篇: Show all branches that commit A is on and commit B is not on?