C++ Logo

std-discussion

Advanced search

some containers always copy constructible according to type_traits

From: Aleksander Maciej Miera <maciej.miera_at_[hidden]>
Date: Sun, 2 Oct 2022 23:00:41 +0200
Hello,

While fiddling with template metaprogramming I managed to run into an
issue related to some std:: containers and type traits. As far as I have
researched it, it is a known (although not obvious at first) shortcoming.

Consider the following code snippet:

#include <type_traits>
#include <memory>
#include <vector>

struct Copyable{};
using MoveOnly = std::unique_ptr<Copyable>;

static_assert(!std::is_copy_constructible_v<std::vector<MoveOnly>>);

(BTW, the vector is used here as an example, but the list and
forward_list also behave the same way for the same reason)

Counter-intuitively, the static assert fails. This stems from the fact
that the vector's copy constructor is not a function template; thus it
cannot be SFINAEd (everything can be a verb if one tries hard enough ;))
away, because it's always declared.
It is logical in terms of how the language works, but at the same time
it is incorrect in terms of API's predictability.

I've been thinking if it can be fixed in some way. For C++11 and 14 I
believe that making the vector's copy constructor a function template
restricted by value_type being copyable should do.
However, that ship seems to have sailed in C++17 and further versions,
as vector is required by the standard to support incomplete types.

I wonder if this issue can be addressed by specialising
std::is_copy_constructible, std::is_copy_assignable and similar traits
based on vector's value_type, e.g.:

template <typename>
struct is_vector_copy_constructible;

template <typename T>
struct is_vector_copy_constructible<std::vector<T>>
     : std::conditional_t<std::is_copy_constructible_v<T>,
std::true_type, std::false_type>
{};

template<typename T>
inline bool constexpr is_vector_copy_constructible_v =
is_vector_copy_constructible<T>::value;

static_assert(!is_vector_copy_constructible_v<std::vector<MoveOnly>>);
static_assert(is_vector_copy_constructible_v<std::vector<Copyable>>);

(for the sake of simplicity all allocator-related subtleties are omitted)

The obvious result would be the change of behaviour for incomplete
types. Therefore code like this:

struct Incomplete;
static_assert(!is_vector_copy_constructible_v<std::vector<Incomplete>>
|| is_vector_copy_constructible_v<std::vector<Incomplete>>);

would fail to compile. It seems a reasonable way of the compiler
communicating "I have no idea", but it would be a breaking change should
is_vector_copy_constructible become a partial specialisation of
is_copy_constructible.

The question is: is it even the right direction I'm headed with my
question? If so, is there a better way to address this or maybe it is a
"broken by design" case and I shouldn't even bother?

In case they're needed, live demos of my code snippets are here:
https://godbolt.org/z/za8q63njE

Best regards,
A. M. Miera

Received on 2022-10-02 21:00:45