SFINAE удалите конструктор копирования

В определенных условиях я бы хотел, чтобы SFINAE удалил конструктор копирования и оператор присваивания копии шаблона класса. Но если я это сделаю, создаются конструктор копии по умолчанию и оператор присваивания по умолчанию. SFINAE выполняется на основе тегов, которые я передаю как параметры шаблона класса. Проблема в том, что SFINAE работает только с шаблонами, а оператор-конструктор/присваивание копий не может быть шаблоном. Существует ли обходной путь?

Ответ 1

Это решение использует базовый класс, который условно не копируется (явно помещая конструктор копирования и оператор назначения копирования как удаленный).

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
};

Пример использования:

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!
}

Примечание. Деструктор NoCopy объявляется protected чтобы избежать виртуального наследования (спасибо за подсказку @Yakk).

Ответ 2

Метод получения из скопируемой или не скопируемой базы является стандартной идиомой для этого типа проблемы (см. Также комментарий Стефана). Один из способов его реализации:

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> { /* ... */ };

Ответ 3

В С++ 20 мы можем использовать предложения require для ограничения специальных функций-членов:

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
    {
        // ...
    }
    // ...
};

Предложение require не делает функцию шаблоном функции, поэтому эти функции по-прежнему считаются специальными функциями-членами и блокируют созданные специальные функции-члены по умолчанию. Вы можете даже иметь несколько, скажем, конструкторов копирования, если они имеют различные ограничения.