C++ Logo

std-discussion

Advanced search

Re: some containers always copy constructible according to type_traits

From: Lénárd Szolnoki <cpp_at_[hidden]>
Date: Mon, 03 Oct 2022 10:11:48 +0100
Hi,

On 2 October 2022 23:38:58 BST, Barry Revzin via Std-Discussion <std-discussion_at_[hidden]> wrote:
>On Sun, Oct 2, 2022 at 5:25 PM Edward Catmur <ecatmur_at_[hidden]> wrote:
>
>>
>>
>> On Sun, 2 Oct 2022 at 22:58, Barry Revzin <barry.revzin_at_[hidden]> wrote:
>>
>>>
>>>
>>> On Sun, Oct 2, 2022 at 4:47 PM Edward Catmur via Std-Discussion <
>>> std-discussion_at_[hidden]> wrote:
>>>
>>>> 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.
>>>>
>>>
>>> The issue is that vector<T> supports T being incomplete. So if you did
>>> that, then:
>>>
>>> struct Node;
>>> vector<Node> nodes;
>>>
>>> Would instantiate the copy constructor which would error on Node being
>>> incomplete. So you'd have to come up with a way for vector<Incomplete> to
>>> still work.
>>>
>>
>> Isn't that ill-formed already as it instantiates the default and allocator
>> ctors? Or have I misunderstood [vector.overview]/4?
>>
>
>Fine:
>
>struct Node {
> vector<Node> nodes;
>};

Interestingly clang and gcc have different opinion on cases like this. You do have to break the circular dependency (Node is copyable if vector<Node> is copyable, and vice versa), but once you do that, clang is happy with it.

https://godbolt.org/z/M51z57PGa

I think gcc is unfortunately right here, but maybe it could be worked around by removing the actual copy constructor with requires, and creating a template "copy constructor" in its place to defer some instantiations.

Anyway, even if that is possible, the main culprit for lack of usability is the implicit circular dependency on constraints if you just let the copy operations to be defaulted.

Cheers,
Lénárd

Received on 2022-10-03 09:11:53