1
#include <vector>
#include <memory>

template<typename T>
class V {
public:
    template<typename U = T, std::enable_if_t<std::is_copy_assignable_v<U>, int> = 0>
    auto operator = (const V &rhs) -> V & { v = rhs.v; return *this; }
private:
    std::vector<T> v;
};

template<typename T>
class U {
public:
    template<typename U = T, std::enable_if_t<std::is_copy_assignable_v<U>, int> = 0>
    auto operator = (const U &rhs) -> U & { t = rhs.t; return *this; }
private:
    T t;
};

int main()
{
    static_assert(!std::is_copy_assignable_v<std::unique_ptr<int>>);    // success
    static_assert(!std::is_copy_assignable_v<U<std::unique_ptr<int>>>); // success
    static_assert(!std::is_copy_assignable_v<V<std::unique_ptr<int>>>); // fail
    return 0;
}

Here, U<T> and V<T> have assignment operator when T is copy-assignable.

However, static_assert to check if V<std::unique_ptr<int>> is non-copy-assignable fails although similar check for U<std::unique_ptr<int>> successes.

Why does the static_assert fail for V<T> here and how can I fix it?

slyx
  • 2,063
  • 1
  • 19
  • 28
  • 1
    The default copy assignment operator is never a template. To disable it based on a template parameter, conditionally inherit from a non-copyable base class – Mestkon Aug 04 '22 at 11:15
  • @Meskton Thank you! Why don't you post your comment as answer? – slyx Aug 04 '22 at 11:17

3 Answers3

3

Since C++20 this can be done simpler than using a conditional base, instead using a requires clause to enable/disable the default implementation of the copy assignment:

template<typename T>
class V {
public:
    V(const V&) requires std::is_copy_constructible_v<T> = default;
    V(V&&) = default;

    auto operator=(const V&) -> V& requires std::is_copy_assignable_v<T> = default;
    auto operator=(V&&) -> V& = default;
private:
    std::vector<T> v;
};

Although I think you really want (std::is_copy_assignable_v<T> && std::is_copy_constructible_v<T>) because the copy assignment of std::vector<T> may be using either. Or even better std::copyable<T> which is a concept that additionally verifies some details that are required by the containers, e.g. that the copy assignment returns T& and so on.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • Or even std::copyable... – Jeff Garrett Aug 04 '22 at 13:54
  • @JeffGarrett Yes true. Just `std::is_copy_assignable_v` and `std::is_copy_constructible_v` is not enough to satisfy [_CopyAssignable_](https://en.cppreference.com/w/cpp/named_req/CopyAssignable) which is what the containers use as a requirement. – user17732522 Aug 04 '22 at 18:06
2

The special copy assignment operator is never a template. In cpp insights we can see that the compiler will generate a non-template copy assignment operator in addition to the template operator you provided.

You can conditionally disable special member functions of template class based on template parameter by inheriting from a base class with desired semantics

#include <type_traits>

class copyable { };
class no_copy_assign { void operator=(const no_copy_assign&) = delete; };

template<class T>
class A :
    std::conditional_t</* Condition */, copyable, no_copy_assign>
{ };
// Condition can be std::is_copy_assignable_v<T>
Mestkon
  • 3,532
  • 7
  • 18
2

This is how I would do it, with a constexpr function to evaluate copyable conditions. Compile time test code attached.

#include <type_traits>

class copyable
{
};

class non_copyable
{
public:
    non_copyable(const non_copyable&) = delete;
    non_copyable& operator=(const non_copyable&) = delete;
};

template<typename type_t>
constexpr bool is_copyable()
{
    return
        std::is_copy_constructible_v<type_t> &&
        std::is_copy_assignable_v<type_t>;
}


template<typename type_t>
class my_class_t :
    public std::conditional_t<is_copyable<type_t>(), copyable, non_copyable>
{
};

class test
{
public:
    test() = default;
    ~test() = default;
    test(const test&) = delete;
    test& operator=(const test&) = delete;
};


int main()
{
    static_assert(!std::is_copy_constructible_v<test>);
    static_assert(!std::is_copy_constructible_v<my_class_t<test>>);
    return 0;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19