Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to make is_copy_constructible for container yield false if underlying type is not copy constructible

This is a follow-up to std::unordered_map<T,std::unique_ptr<U>> copyable? GCC bug?

So imagine we created a template class Container:

template<class T>
class Container {
    T t;
public:
    Container() = default;
    Container(const Container& other) : t(other.t) {}
};

Unfortunately, is_copy_constructible for it yields true even if T is not copy constructible:

static_assert(!std::is_copy_constructible<Container<std::unique_ptr<int>>>::value, "Copyable");

This assert fails for the reasons described in the answer to question above, also here is another answer on this topic.

It appears that this can be fixed by making the copy consructor template like this:

template<class T>
class Container {
    T t;
public:
    Container() = default;

    template<typename U = void>
    Container(const Container& other) : t(other.t) {}
};

This works in both GCC and clang (static_assert doesn't fail anymore).

Ideone Demo

The questions:

  1. From the viewpoint of standard, is this a correct way to make is_copy_constructible work? If yes, how does adding the template affect the validity of the immediate context of the variable initialization (§20.9.4.3/6)?

  2. (optional) Are there any more correct or more intuitive ways to do this?

Note: declaring copy constructor default also achieves this objective, but is not always possible.

UPDATE: Now I see that my solution is invalid because copy constructor cannot be template. That still leaves room for question 2.

UPDATE 2: I changed a bit the code from ecatmur's answer to move ugliness out of Container itself and make it reusable:

struct unused;  // forward declaration only
template<class Container>
using const_ref_if_copy_constructible = typename std::conditional<
        std::is_copy_constructible<typename Container::value_type>::value,
        Container const&,
        unused>::type;

template<typename T>
class Container {
    T t;
public:
    typedef T value_type;
    Container() = default;

    Container(const_ref_if_copy_constructible<Container> other) : t(other.t) {}
    Container(Container&& other) : t(std::move(other.t)) {}
};

(Demo)

But still I'm not quite satisfied with this. For me it looks like a flaw in C++ standard that such things don't work out of the box.

like image 471
Anton Savin Avatar asked Sep 12 '14 13:09

Anton Savin


1 Answers

That's not doing what you think; a template constructor is never considered to be a copy constructor, so by adding template<typename U = void> to the copy constructor you're inducing the compiler to create its own default copy constructor.

A possibility (short of having separate class templates for non-copy-constructible types) would be to disable the copy constructor by replacing its argument with something that will be irrelevant for overload resolution:

struct unused;  // forward declaration only

template<typename T>
class Container {
    T t;
public:
    Container() = default;

    Container(
      typename std::conditional<
        std::is_copy_constructible<T>::value,
        Container const&,
        unused>::type other)
      : t(other.t) {}

    Container(Container&& other) : t(std::move(other.t)) {}
};
like image 193
ecatmur Avatar answered Oct 30 '22 13:10

ecatmur