Suppose I\'m writing a class template C
that holds a T
value, so C
can be copyable only if T
is copyabl
This is a bit of a trick, but it works.
template<bool b,class T>
struct block_if_helper{
using type=T;
};
template<class T>
struct block_if_helper<true, T>{
class type{
type()=delete;
};
};
template<bool b,classT>
using block_if=typename block_if_helper<b,T>::type;
template<bool b,classT>
using block_unless=typename block_if_helper<!b,T>::type;
now we create a method that is your copy ctor ... maybe.
template<class X>
struct example {
enum { can_copy = std::is_same<X,int>{} };
example( block_unless<can_copy, example>const& o ); // implement this as if `o` was an `example`
// = default not allowed
example( block_if<can_copy, example>const& )=delete;
};
and now the =default
is the copy ctor if and only if can_copy
, and the =delete
of not. The stub type that it is otherwise cannot be created.
I find this technique useful for general method disabling on compilers that do not support the default template argument feature, or for methods (like virtual
or special) that cannot be template
s.
However, it's not always possible to structure your class so that defaulted constructors will do the right thing.
It's usually possible with enough effort.
Delegate the work that can't be done by a defaulted constructor to another member, or wrap the T
member in some wrapper that does the copying, or move it into a base class that defines the relevant operations.
Then you can define the copy constructor as:
C(const C&) = default;
Another way to get the compiler to decide whether the default definition should be deleted or not is via a base class:
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
};
This can be used to delete operations using arbitrary conditions, such as is_nothrow_copy_constructible
rather than just a straightforward T is copyable implies C is copyable rule.
C::C(C const& rhs, std::enable_if<true, int>::type dummy = 0)
is also a copy ctor because the second argument has a default value.
A noteworthy approach is partial specialization of the surrounding class template.
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;
};
This way the trait is_copy_constructible
is satisfied exactly where T
is_copy_constructible
.
If you want to conditionally disable your copy constructor, you definitely want it to participate in overload resolution - because you want it to be a loud compile error if you try to copy it.
And to do that, all you need is static_assert
:
template <typename T>
class C {
public:
C(const C& rhs) {
static_assert(some_requirement_on<T>::value,
"copying not supported for T");
}
};
This will allow copy construction only if some_requirement_on<T>
is true, and if it's false, you can still use the rest of the class... just not copy construction. And if you do, you'll get a compile error pointing to this line.
Here's a simple example:
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
}
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() {};
};
This makes a non-eligible template instance have two copy constructors, which makes it not copy constructible.