如何编写一个模板,它将作为参数类,其构造函数具有互斥的签名?
class A
{
A(){};
public:
int a;
A(int i) : a(i) {};
};
class B{
B(){};
public:
int a,b;
B(int i,int j) : a(i), b(j) {};
};
template <class T> class C {
public:
T* _t;
C(int i[])
{ //???
_t=new T(i[0]); //if T has T(int) signature
_t=new T(i[0],i[1]); //if T has T(int,int) signature
}
~C() {delete _t;}
};
int main()
{
int Ai[]={1,2};
C<A> _c(Ai); // template should work instantiated with A and B
C<B> _c(Ai); //
return 0;
}
A
和B
的签名是固定的(不能更改为int [],例如)。上下文:我正在考虑一个包装器,它将一个(专用的)容器类型作为模板参数,例如T=vector<int>
或T=map<int,int>
,当需要调用构造函数时会出现问题。
答案 0 :(得分:4)
使用可变模板化的构造函数:
template <typename T> struct C
{
template <typename ...Args> C(Args &&... args)
: _t(new T(std::forward<Args>(args)...))
{
}
// ... destructor? Rule of five? Don't use pointers!!!
private:
T * _t; // ouch!
};
用法:
C<A> x(1);
C<B> y(2, 3);
(真正的程序员当然更喜欢成员std::unique_ptr<T> _t;
,其他语义不变,但允许你忽略所有的评论。)
答案 1 :(得分:0)
我相信Kerrek SB的答案部分正确,但不完整。失败的原因是C<T>
的构造函数过于通用。也就是说,如果只看一下它的构造函数声明,C<T>
将从任何东西构造。在您选择构造函数并进行实例化之前,您不会发现其他情况。到那时为时已晚。
具体例子:
假设C<T>
有:
friend bool operator<(const C&, const C&);
现在你想在C<T>
:
map
密钥
std::map<C<A>, int> m;
// ...
m.erase(m.begin());
这是一个错误,因为有两个erase
重载现在看起来像:
iterator erase(const_iterator position);
size_type erase(const key_type& k);
和m.begin()
是iterator
。这个iterator
同样可以轻松转换为const_iterator
和key_type
(又名C<A>
)。
现在可以通过调用:
来解决这个问题m.erase(m.cbegin());
代替。但这只是过度通用构造函数导致的问题的冰山一角。例如,任何分支的代码:
std::is_constructible<C<A>, any type and any number of them>::value
可能会出现误报,因为上面的总是会返回true。
修复有点乱,但非常实用:
template<typename T>
struct C
{
template <class ...Args,
class = typename std::enable_if
<
std::is_constructible<T, Args...>::value
>::type
>
C(Args&& ...args)
: _t(new T(std::forward<Args>(args)...))
{
}
// ...
};
即。向构造函数添加一个约束,以便在它不起作用时不会实例化它。这是混乱,丑陋,无论如何。也许你想用宏来装扮它。精细。但它使这个类工作,其中否则在我上面提到的示例中已经 (以及其他许多其他问题都会像bug一样涓涓细流在一段时间内一次报告一次。)
除了Kerrek SB在这里使用unique_ptr<T>
优于原始指针的好建议之外,我想补充一下:
这个构造函数可能应该是explicit
,至少在实际用例中显示它确实需要隐式。
考虑将T
而不是(可能智能)指针存储到T
。除非你真的试图指向一个基类来实现运行时多态,否则你不需要指针语义。
总结:警惕过于通用的代码,以及对过于通用的构造函数的彻头彻尾的偏执。