Add copy constructor based on template parameters

Viewed 820

I have a container-like class, that I would like to be move-only if the underlying type is move-only, but copyable otherwise. To make things simple, let's assume copyability is determined by a single bool template parameter:

template<bool IsCopyable>
struct Foo
{
    Foo();
    Foo(const Foo&);    // only include this when IsCopyable is true
    Foo(Foo&&);

    Foo& operator=(const Foo&);  // only when IsCopyable
    Foo& operator=(Foo&&);
};

Now, I can't just SFINAE the copy ctor away, because that requires making it templated and a templated function can't be a copy ctor. Also, I can't just do a static_assert() within the copy ctor. Although this will catch erroneous uses of the copy ctor, it also makes the class inherently copy constructible from the outside (the std::is_copy_constructible type trait will yield true).

By the way, an unfortunate requirement is that it needs to compile in VC++ 2012, so I can't use fancy expression SFINAE, inheriting ctors, defaulted/deleted functions or constexpr if (nevertheless, if you have a neat solution for C++17 I'd still like to hear it :))

The obvious method is to use template specialization. I'd rather not go this route, because in reality Foo has quite a lot of functionality and I don't like to repeat myself. Nevertheless, this seems my only option, and I can implement some code sharing using a base class, like so:

// Base functionality
template<bool IsCopyable>
struct FooBase
{
    FooBase();

    // move ctor and assignment can go here
    FooBase(FooBase&&);     
    FooBase& operator=(FooBase&&);

    // some generic conversion ctor and assignment that I happen to need
    template<class T> FooBase(T&& t);
    template<class T> FooBase& operator=(T&&);

    // ... all sorts of functionality and datamembers       
};

// Foo<false>
template<bool IsCopyable>
struct Foo : FooBase<IsCopyable>
{
    // can't use inheriting ctors in VS 2012, wrap the calls manually:
    Foo() { }
    Foo(Foo&& other) : FooBase<IsCopyable>(std::move(other)) { }
    Foo& operator=(Foo&& other)
    {
        FooBase<IsCopyable>::operator=(std::move(other));
        return *this;
    }

    template<class T> Foo(T&& t) : FooBase<IsCopyable>(std::forward<T>(t)) { }
    template<class T> Foo& operator=(T&& t)
    {
        FooBase<IsCopyable>::operator=(std::forward<T>(t));
        return *this;
    }
};

// Foo<true>
template<>
struct Foo<true> : FooBase<true>
{
    // add these
    Foo(const Foo&);
    Foo& operator=(const Foo&);

    // wrapping calls because of VS 2012:
    Foo() { }
    Foo(Foo&& other) : FooBase<true>(std::move(other)) { }
    Foo& operator=(Foo&& other)
    {
        FooBase<true>::operator=(std::move(other));
        return *this;
    }

    template<class T> Foo(T&& t) : FooBase<true>(std::forward<T>(t)) { }
    template<class T> Foo& operator=(T&& t)
    {
        FooBase<true>::operator=(std::forward<T>(t));
        return *this;
    }
};

It's still a bit verbose. Fortunately it gets cleaner once you can use inheriting ctors and defaulted functions. Nevertheless, I was hoping there is a simpler way, ideally by not using a base class.

3 Answers
struct nonesuch {
private:
    ~nonesuch();
    nonesuch(const nonesuch&);
    void operator=(const nonesuch&);
};

template<bool IsCopyable>
struct Foo {
    Foo(const typename std::conditional<IsCopyable, Foo, nonesuch>::type& other) {
        // copy ctor impl here
    }
private:
    Foo(const typename std::conditional<!IsCopyable, Foo, nonesuch>::type&);
};

Likewise for assignment.

Can't you use a base class to selectively disable copying? That way you don't need to repeat any of the other functionality of the main class:

template <bool b>
struct MaybeCopyable {};

template <>
struct MaybeCopyable<false> {
    MaybeCopyable(const MaybeCopyable&) = delete;
    MaybeCopyable& operator=(const MaybeCopyable&) = delete;
};

template<bool IsCopyable>
struct Foo : MaybeCopyable<IsCopyable> {
    // other functionality
};

If MaybeCopyable<false> is one of the base classes, the copy constructor of Foo will be automatically deleted.

I don't know if can work with your VC++2012 but... if I remember correctly, there is a method that i think a little better that inheriting from FooBase.

A sort of auto-inheritance.

You can declare the struct

template <bool>
struct Bar;

and specialize the true version with all constructor/operators

template <>
struct Bar<true>
 {
   Bar ()
    { std::cout << "Bar ()" << std::endl; };

   Bar (Bar &&)
    { std::cout << "Bar (Bar &&)" << std::endl; }

   Bar (Bar const &)
    { std::cout << "Bar (Bar const &)" << std::endl; }

   Bar & operator= (Bar &&)
    { std::cout << "operator= (Bar &&)" << std::endl; return *this; }

   Bar & operator= (Bar const &)
    { std::cout << "operator= (Bar const &)" << std::endl; return *this; }
 };

Now you can specialize the false version inheriting from Bar<true>, confirming (= default) what you want and deleting (= delete) what do you don't want; something like

template <>
struct Bar<false> : public Bar<true>
 {
   using Bar<true>::Bar;

   // confirmed constructor/operators
   Bar ()                   = default;
   Bar (Bar &&)             = default;
   Bar & operator= (Bar &&) = default;

   // deleted constructor/operators
   Bar (Bar const &)             = delete;
   Bar & operator= (Bar const &) = delete;
 };

The following is a full working example

#include <iostream>

template <bool>
struct Bar;

template <>
struct Bar<true>
 {
   Bar ()
    { std::cout << "Bar ()" << std::endl; };

   Bar (Bar &&)
    { std::cout << "Bar (Bar &&)" << std::endl; }

   Bar (Bar const &)
    { std::cout << "Bar (Bar const &)" << std::endl; }

   Bar & operator= (Bar &&)
    { std::cout << "operator= (Bar &&)" << std::endl; return *this; }

   Bar & operator= (Bar const &)
    { std::cout << "operator= (Bar const &)" << std::endl; return *this; }
 };

template <>
struct Bar<false> : public Bar<true>
 {
   using Bar<true>::Bar;

   // confirmed constructor/operators
   Bar ()                   = default;
   Bar (Bar &&)             = default;
   Bar & operator= (Bar &&) = default;

   // deleted constructor/operators
   Bar (Bar const &)             = delete;
   Bar & operator= (Bar const &) = delete;
 };


int main ()
 {
   Bar<true>   bt1;
   Bar<false>  bf1;

   auto bt2 = bt1;
   // auto bf2 = bf1; compilation error (copy constructor deleted)

   auto bt3 = std::move(bt1);
   auto bf3 = std::move(bf1);

   Bar<true>   bt4;
   Bar<false>  bf4;

   bt4 = bt3;
   // bf4 = bf3; compilation error (operator= (Bar const &) deleted)

   bt3 = std::move(bt4);
   bf3 = std::move(bf4);
 }
Related