Under certain conditions, I'd like to SFINAE away the copy constructor and copy assignment operator of a class template. But if I do so, a default copy constructor and a default assignment operator are generated. The SFINAE is done based on tags I pass as class template parameters. The problem is, that SFINAE only works on templates and a copy constructor/assignment operator can't be a template. Does there exist a workaround?
This solution uses a base class that is conditionally not copyable (by explicitely marking the copy constructor and copy assignment operator as deleted).
template <bool>
struct NoCopy;
template <>
struct NoCopy<true>
{
// C++11 and later: marking as deleted. Pre-C++11, make the copy stuff private.
NoCopy(const NoCopy&) = delete;
NoCopy& operator=(const NoCopy&) = delete;
protected:
~NoCopy() = default; // prevent delete from pointer-to-parent
};
template <>
struct NoCopy<false>
{
// Copies allowed in this case
protected:
~NoCopy() = default; // prevent delete from pointer-to-parent
};
Example usage:
template <typename Number>
struct Foo : NoCopy<std::is_integral<Number>::value>
{
Foo() : NoCopy<std::is_integral<Number>::value>{}
{
}
};
int main()
{
Foo<double> a;
auto b = a; // compiles fine
Foo<int> f;
auto g = f; // fails!
}
Note: the destructor of NoCopy
is declared protected
to avoid virtual inheritance (Thanks for the hint, @Yakk).
copyable
is slightly more elegant (because it only specializes the special case). But obviously both approaches work just fine. –
Compendious NoCopy
should have a protected destructor in both cases, as it is a class intended to be inherited from, and we don't want to force virtual
overhead. Just as a matter of quality of implementation. –
Thumbnail <true>
from the body of the class. Within the template class itself, the name of the template class can be used without template arguments to refer to the current class type. This removes a lot of clutter. :) –
Thumbnail Foo<double>
copy constructor in this example? –
Knife Foo<int>
won't compile because NoCopy<int> constructor is deleted, will it? Or we can provide partial specialization of Foo<non-int>
constructor? –
Knife NoCopy<true>
if you really want. After all we need to be able to construct Foo
, and the copy constructor can use the same parent constructor. NoCopy<true>
just disables copying by default. –
Thumbnail The method of deriving from a copyable or non-copyable base is the standard idiom for this type of problem (see also Stefan's comment). One way to implement it is like this:
template<bool> struct copyable
{
protected:
~copyable() = default;
};
template<> struct copyable<false>
{
copyable(copyable const&) = delete;
copyable&operator=(copyable const&) = delete;
protected:
~copyable() = default;
};
template<bool allow_copy>
class some_class : copyable<allow_copy> { /* ... */ };
some_class
. And fianlly, copyable
should have protected: ~copyable()=default
in both cases. –
Thumbnail copyable
a base-only class? –
Stlaurent copyable
from calling delete
on it: any class intended to be derived from should (usually) have a protected
destructor, or a virtual
destructor, as deleting base-type is one of those hard to track down "oops" cases. In this case, an instance of copyable
(that isn't a base class) is clearly nonsense, so there is no harm either. –
Thumbnail man gcc
or man clang
. –
Farber In C++20, we can use requires-clauses to constrain special member functions:
template <typename T>
class C {
public:
// ...
C(const C&) requires std::is_copy_constructible_v<T> // for example
{
// ...
}
C(C&&) requires std::is_move_constructible_v<T> // for example
{
// ...
}
// ...
};
A requires-clause does not make the function a function template, so these functions still qualify as special member functions and block the generated default special member functions. You can even have multiple, say, copy constructors, as long as they have different constraints.
© 2022 - 2024 — McMap. All rights reserved.