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
仍然非常轻量级,应该内联。容易腻。
C ++ 11引入了可变参数模板。那么典型的冲动就是用C ++ 11可变参数模板重新实现所有不安全的C-variadics,甚至维基百科也用a printf
implementation建议。
不幸的是,维基百科的实现并不涉及位置参数:允许你在那里指定打印第3个参数的类型等等......如果我们只有这个原型的函数那么会很容易:
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))...);
}
然而,你会注意到一个补充步骤的介绍,这有点烦人。不过,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)...});
}
但仍未解决中间功能问题。
答案 0 :(得分:3)
使其轻量化取决于消除类型参数化。你的垫片可能会使用表达式out << _t
来实例化一些重要的东西,所以它可能不是一个很好的例子。
C varargs通过隐式地将所有内容转换为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
的意思。它只能从braced-init-list构造,使其大小为编译时常量。但是大小只能作为运行时常量读回。因此,它唯一的实际用途是将仅在列表长度上不同的漏洞模板汇总到一个通用的可变长度实现。
添加initializer_list
参数的生命周期语义 - 对象在堆栈上的连续数组中创建,并在函数调用语句结束时死亡 - 而initializer_list
看起来很像{{1} }}! (编辑:或者您的解决方案,我现在实际上回过头来阅读:vP)
编辑:由于容器无法直接存储多态对象,并且智能指针不适合临时参数对象,因此实现多态会需要指向临时对象。丑陋,但由于临时对象的寿命保证合法:
<varargs>
答案 1 :(得分:0)
如果您可以使用同质(内存中相同的大小和对齐)类型,请查看at that:
// 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个字节,则强制所有其他元素为64个字节即使它们是1个字节也是对齐的。)因此需要复制,或者至少复制到指向固定位置的指针,所以它绝对是不可能 OP想要什么。
我们仍然可以构建该数组,但我们受到限制:
我们根本不想复制,那么我们应该静态声明数组的类型,这迫使我们构建第三种类型。
auto Array = MakeArray( /* values*/);
printf( Array);
我们接受复制,所以我们在函数内部构建数组,因为值不知道我们可以从用户隐藏数组但是我们必须将参数复制到固定的内存位置,但是我们仍然将数组隐藏在引擎盖。
堆分配,允许在非常紧凑的数组中传递参数,但参数必须驻留在其他地方,并且堆分配可能成本很高。
第一种解决方案是通过创建一个可以寻址元素的静态类型数组来接受编码中的额外复杂性(所有这些数据都与最大元素对齐),然而这是次优的,因为这会增加对象的大小,无论如何都可以达到性能(如果该数组在函数调用之后仍然存在)
第二种解决方案隐藏了模板界面背后的复杂性,但它无法避免将值临时复制到与第一种解决方案相同的阵列的性能成本。
因此,不可能这样做,抱歉。另一个答案介于2和3之间。所有其他可能的答案都属于3个类别中的一个。