【发布时间】:2015-01-20 06:58:46
【问题描述】:
假设我正在编写一个类模板C<T>,它包含一个T 值,那么只有当T 是可复制的时,C<T> 才能是可复制的。通常,当模板可能支持或可能不支持某个操作时,您只需定义该操作,并由调用者在不安全时避免调用它:
template <typename T>
class C {
private:
T t;
public:
C(const C& rhs);
C(C&& rhs);
// other stuff
};
但是,这在复制构造函数的情况下会产生问题,因为即使T 不可复制,is_copy_constructible<C<T>> 也会为真;如果调用该特征,则该特征看不到复制构造函数的格式错误。 这是一个问题,例如,如果std::is_copy_constructible 为真,vector 有时会避免使用移动构造函数。我该如何解决这个问题?
如果构造函数显式或隐式默认,我相信is_copy_constructible 会做正确的事:
template <typename T>
class C {
private:
T t;
public:
C(const C& rhs) = default;
C(C&& rhs) = default;
// other stuff
};
但是,并非总是可以构造您的类,以便默认构造函数执行正确的操作。
我能看到的另一种方法是使用 SFINAE 有条件地禁用复制构造函数:
template <typename T>
class C {
private:
T t;
public:
template <typename U = C>
C(typename std::enable_if<std::is_copy_constructible<T>::value,
const U&>::type rhs);
C(C&& rhs);
// other stuff
};
除了丑陋之外,这种方法的问题在于我必须将构造函数设为模板,因为 SFINAE 仅适用于模板。根据定义,复制构造函数不是模板,所以我禁用/启用的东西实际上不是复制构造函数,因此它不会抑制编译器隐式提供的复制构造函数。
我可以通过显式删除复制构造函数来解决这个问题:
template <typename T>
class C {
private:
T t;
public:
template <typename U = C>
C(typename std::enable_if<std::is_copy_constructible<T>::value,
const U&>::type rhs);
C(const C&) = delete;
C(C&& rhs);
// other stuff
};
但这仍然不能阻止在重载决议期间考虑复制构造函数。这是一个问题,因为在其他条件相同的情况下,普通函数将在重载决议中击败函数模板,因此当您尝试复制 C<T> 时,普通的复制构造函数会被选中,即使 T 也会导致构建失败是可复制的。
我能发现原则上可行的唯一方法是从主模板中省略复制构造函数,并以部分特化的方式提供它(当 T 不可复制时,使用更多的 SFINAE 技巧来禁用它)。但是,这很脆弱,因为它需要我复制C 的整个定义,这会产生两个副本不同步的重大风险。我可以通过让方法体共享代码来缓解这种情况,但我仍然必须复制类定义和构造函数成员初始化列表,这为错误潜入提供了足够的空间。我可以通过让它们都继承来进一步缓解这种情况来自一个公共基类,但引入继承可能会产生各种不受欢迎的后果。此外,当我想要做的只是禁用一个构造函数时,公共继承似乎是适合这项工作的错误工具。
有没有我没有考虑过的更好的选择?
【问题讨论】:
标签: c++ templates constructor sfinae