Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add copy constructor based on template parameters

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.

like image 574
oisyn Avatar asked Nov 17 '17 02:11

oisyn


2 Answers

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.

like image 36
Brian Bi Avatar answered Nov 07 '22 07:11

Brian Bi


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.

like image 173
T.C. Avatar answered Nov 07 '22 07:11

T.C.