Geo*_*mer 31 c++ templates constructor sfinae
假设我正在编写一个C<T>
包含T
值的类模板,因此C<T>
只有在可复制的情况下才T
可以复制.通常,当模板可能支持或可能不支持某个操作时,您只需定义操作,并且由您的调用者决定是否在不安全时调用它:
template <typename T>
class C {
private:
T t;
public:
C(const C& rhs);
C(C&& rhs);
// other stuff
};
Run Code Online (Sandbox Code Playgroud)
但是,这会在复制构造函数的情况下产生问题,因为is_copy_constructible<C<T>>
即使T
不可复制也是如此; 如果被调用,则特征无法看到复制构造函数将是错误的.而且这是一个问题,因为,例如,vector
有时会避免使用移动构造函数,如果std::is_copy_constructible
是真实的.我怎样才能解决这个问题?
is_copy_constructible
如果构造函数显式或隐式默认,我相信会做正确的事情:
template <typename T>
class C {
private:
T t;
public:
C(const C& rhs) = default;
C(C&& rhs) = default;
// other stuff
};
Run Code Online (Sandbox Code Playgroud)
但是,并不总是可以构建您的类,以便默认的构造函数将做正确的事情.
我能看到的另一种方法是使用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
};
Run Code Online (Sandbox Code Playgroud)
除了像罪一样丑陋之外,这种方法的麻烦在于我必须使构造函数成为模板,因为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
};
Run Code Online (Sandbox Code Playgroud)
但是,这仍然无法阻止在重载解析期间考虑复制构造函数.这是一个问题因为其他条件相同,普通函数会在重载决策中击败函数模板,因此当您尝试复制a时C<T>
,普通复制构造函数会被选中,即使T
可复制也会导致构建失败.
我能找到的唯一方法是原则上可以省略主模板中的复制构造函数,并以部分特化方式提供它(当T不可复制时,使用更多的SFINAE技巧来禁用它).但是,这很脆弱,因为它要求我复制整个定义C
,这会造成两个副本不同步的主要风险.我可以通过让方法体共享代码来缓解这种情况,但是我仍然需要复制类定义和构造函数member-init列表,并且这有足够的空间让bug潜入.我可以通过让它们都继承来进一步减轻这种情况从一个共同的基类,但引入继承可能会产生各种不受欢迎的后果.此外,当我尝试做的只是禁用一个构造函数时,公共继承似乎是工作的错误工具.
有没有更好的选择,我没有考虑过?
Col*_*mbo 43
值得注意的方法是对周围类模板进行部分特化.
template <typename T,
bool = std::is_copy_constructible<T>::value>
struct Foo
{
T t;
Foo() { /* ... */ }
Foo(Foo const& other) : t(other.t) { /* ... */ }
};
template <typename T>
struct Foo<T, false> : Foo<T, true>
{
using Foo<T, true>::Foo;
// Now delete the copy constructor for this specialization:
Foo(Foo const&) = delete;
// These definitions adapt to what is provided in Foo<T, true>:
Foo(Foo&&) = default;
Foo& operator=(Foo&&) = default;
Foo& operator=(Foo const&) = default;
};
Run Code Online (Sandbox Code Playgroud)
这种方式的特性is_copy_constructible
恰好满足于何处T
is_copy_constructible
.
但是,并不总是可以构建您的类,以便默认的构造函数将做正确的事情.
通常可以付出足够的努力.
将默认构造函数无法完成的工作委托给另一个成员,或者将该T
成员包装在执行复制的某个包装器中,或将其移动到定义相关操作的基类中.
然后,您可以将复制构造函数定义为:
C(const C&) = default;
Run Code Online (Sandbox Code Playgroud)
让编译器决定是否应该删除默认定义的另一种方法是通过基类:
template<bool copyable>
struct copyable_characteristic { };
template<>
struct copyable_characteristic<false> {
copyable_characteristic() = default;
copyable_characteristic(const copyable_characteristic&) = delete;
};
template <typename T>
class C
: copyable_characteristic<std::is_copy_constructible<T>::value>
{
public:
C(const C&) = default;
C(C&& rhs);
// other stuff
};
Run Code Online (Sandbox Code Playgroud)
这可以用于使用任意条件删除操作,例如,is_nothrow_copy_constructible
而不仅仅是简单的T是可复制的,暗示C是可复制的规则.
如果要有条件地禁用复制构造函数,您肯定希望它参与重载解析 - 因为如果您尝试复制它,则希望它是一个响亮的编译错误.
要做到这一点,你所需要的只是static_assert
:
template <typename T>
class C {
public:
C(const C& rhs) {
static_assert(some_requirement_on<T>::value,
"copying not supported for T");
}
};
Run Code Online (Sandbox Code Playgroud)
这将允许复制构造只有在some_requirement_on<T>
为真,如果它是假的,你仍然可以使用该类的其余部分...只是不复制构造.如果你这样做,你将得到一个指向这一行的编译错误.
这是一个简单的例子:
template <typename T>
struct Foo
{
Foo() { }
Foo(const Foo& ) {
static_assert(std::is_integral<T>::value, "");
}
void print() {
std::cout << "Hi" << std::endl;
}
};
int main() {
Foo<int> f;
Foo<int> g(f); // OK, satisfies our condition
g.print(); // prints Hi
Foo<std::string> h;
//Foo<std::string> j(h); // this line will not compile
h.print(); // prints Hi
}
Run Code Online (Sandbox Code Playgroud)
template <typename T>
class variant {
struct moo {};
public:
variant(const variant& ) = default;
variant(std::conditional_t<!std::is_copy_constructible<T>::value,
const variant&, moo>,
moo=moo());
variant() {};
};
Run Code Online (Sandbox Code Playgroud)
This makes a non-eligible template instance have two copy constructors, which makes it not copy constructible.