The correct way to create is_copy_constructible for a container is false if the base type is not constructive for copying

This is a continuation of std :: unordered_map <T, std :: unique_ptr <U โ†’ gt; Copyable? GCC error?

So, imagine that we created the Container template class:

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

Unfortunately, is_copy_constructible for it gives true , even if T not constructive for copying:

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

This statement is not suitable for the reasons described in the answer to the question above, also here is another answer to this question .

It looks like this can be fixed by creating a copy constructor template as follows:

 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 no longer works).

Perfect demonstration

Questions:

  • From a standard point of view, is this the right way to make is_copy_constructible work? If so, how does adding a template affect the validity of the immediate context of variable initialization ( ยง20.9.4.3/6 )?

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

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

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

UPDATE 2: I changed the code from ecatmur answer a bit to move the ugliness from Container 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 happy with this. For me, it looks like a flaw in the C ++ standard that such things do not work out of the box.

+6
source share
3 answers

This is not what you think; the template constructor is never considered a copy constructor, so by adding template<typename U = void> to the copy constructor, you call the compiler to create your own default copy constructor.

The possibility (with the exception of having separate class templates for types that do not have copies) would have to disable the copy constructor, replacing its argument with something that does not matter to allow overloading:

 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)) {} }; 
+4
source

Not quite an answer, as a detailed comment: one of the advantages of Concepts Lite is the ability to contain functions without requiring them as templates, as is the case with SFINAE. Lite concepts will make this problem trivial:

 template <typename T> concept bool Copyable = requires(const T source, T dest) { T{source}; // copy construction dest = source; // copy assignment }; template <typename T> class Container { T t; public: Container() = default; Container(const Container& other) requires Copyable<T> : t(other.t) {} }; 
+2
source

An alternative to ecatmur's answer is the following idea, in which you get a template base class that is either constructive or independent of the template parameter.

 template<bool> struct copyable {}; template<> struct copyable<false> { copyable() = default; // default constructible copyable(copyable const&) = delete; // but not copyable }; template<typename T> class container : copyable<std::is_copy_constructible<T>::value> { T t; public: container() = default; container(container const&) = default; }; 

Please note that for this, i.e. for std::is_copy_constructible<container<std::unique_ptr<int>>>::value==false , the copy constructor constructor should be default .

+1
source

Source: https://habr.com/ru/post/975195/


All Articles