Date: Sun, 2 Oct 2022 22:47:18 +0100
On Sun, 2 Oct 2022 at 22:00, Aleksander Maciej Miera via Std-Discussion <
std-discussion_at_[hidden]> wrote:
> 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.
>
Since C++20, the copy constructor can be constrained:
vector(vector const&) requires std::copyable<T>;
I think this would solve your issue. This would require the containers
general requirements to lift this requirement (on `X u(a)`, that `T` is
Cpp17CopyInsertable, etc.) from a precondition to a constraint. There are
probably quite a few clauses that would need to be considered in such an
effort.
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
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>
std-discussion_at_[hidden]> wrote:
> 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.
>
Since C++20, the copy constructor can be constrained:
vector(vector const&) requires std::copyable<T>;
I think this would solve your issue. This would require the containers
general requirements to lift this requirement (on `X u(a)`, that `T` is
Cpp17CopyInsertable, etc.) from a precondition to a constraint. There are
probably quite a few clauses that would need to be considered in such an
effort.
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
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>
Received on 2022-10-02 21:47:31