Date: Sun, 28 Jul 2024 17:20:21 -0400

On Sun, Jul 28, 2024 at 10:20 AM Hewill Kang via Std-Proposals <

std-proposals_at_[hidden]> wrote:

>

> Quoting the original paper’s description

> <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p0843r14.html#Deduction-guides>

> for deduction guides for std::inplace_vector: "*Unlike the other

> containers, inplace_vector** does not have any deduction guides because

> there is no case in which it would be possible to deduce the second

> template argument, the capacity, from the initializer.*"

>

> However, I found that in some cases it is possible to enable CTAD for

> std::inplace_vector, for example when we accept a std::array or std::span:

>

> template<class T, size_t N>

> inplace_vector(from_range_t, const std::array<T, N>&) ->

> inplace_vector<T, N>; // new CTAD

> Then we can:

>

> std::inplace_vector v{std::from_range, std::array{1, 2, 3}};

>

The array has size 3 *and capacity 3*, whereas the inplace_vector has size

3 *and capacity unknown*.

You're asking why the (initial) size of an inplace_vector might ever be

different from its capacity... but that's literally the reason that

inplace_vector exists! If you want a container whose size is always equal

to its capacity, you could just use `array` directly.

I think this makes meta-programming more user-friendly and convenient, for

> example, converting index_sequence into inplace_vector and then applying

> the algorithm in <algorithm>:

>

> constexpr inplace_vector v = []<size_t... Is>(index_sequence<Is...>) {

> inplace_vector v{from_range, array{Is...}};

> auto [begin, end] = ranges::remove(v, 42);

> v.erase(begin, end);

> return v;

> }(std::make_index_sequence<N>{});

> Would it be valuable for inplace_vector to introduce std::array or

> std::span-specific CTADs?

>

IMO, no it would not.

Notice that what you have there is equivalent in effect to:

constexpr auto v = []<size_t... Is>(index_sequence<Is...>) {

size_t v[] = {Is...};

auto [begin, end] = std::ranges::remove(v, 42);

return std::inplace_vector<size_t, *sizeof...(Is)*>(v, begin);

}(std::make_index_sequence<N>{});

`*sizeof...(Is)*` is almost certainly the *wrong* capacity, there. Either

you're planning to add more elements to it (so you need more capacity), or

you're not (in which case you should use a capacity equal to the *final*

size, not the size *before* the remove).

I also have a vague sense that since inplace_vector is expensive to

move-construct, we really don't want to encourage people to create

inplace_vectors without thinking about capacity — and then have to move

them into the "proper" type later on. We can get away with that for types

like `shared_ptr<U>` and `unique_ptr<U>`, where converting to the correct

type is O(1). But for `inplace_vector<T, M>`, converting to

`inplace_vector<T, N>` is an O(M) operation.

HTH,

Arthur

std-proposals_at_[hidden]> wrote:

>

> Quoting the original paper’s description

> <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p0843r14.html#Deduction-guides>

> for deduction guides for std::inplace_vector: "*Unlike the other

> containers, inplace_vector** does not have any deduction guides because

> there is no case in which it would be possible to deduce the second

> template argument, the capacity, from the initializer.*"

>

> However, I found that in some cases it is possible to enable CTAD for

> std::inplace_vector, for example when we accept a std::array or std::span:

>

> template<class T, size_t N>

> inplace_vector(from_range_t, const std::array<T, N>&) ->

> inplace_vector<T, N>; // new CTAD

> Then we can:

>

> std::inplace_vector v{std::from_range, std::array{1, 2, 3}};

>

The array has size 3 *and capacity 3*, whereas the inplace_vector has size

3 *and capacity unknown*.

You're asking why the (initial) size of an inplace_vector might ever be

different from its capacity... but that's literally the reason that

inplace_vector exists! If you want a container whose size is always equal

to its capacity, you could just use `array` directly.

I think this makes meta-programming more user-friendly and convenient, for

> example, converting index_sequence into inplace_vector and then applying

> the algorithm in <algorithm>:

>

> constexpr inplace_vector v = []<size_t... Is>(index_sequence<Is...>) {

> inplace_vector v{from_range, array{Is...}};

> auto [begin, end] = ranges::remove(v, 42);

> v.erase(begin, end);

> return v;

> }(std::make_index_sequence<N>{});

> Would it be valuable for inplace_vector to introduce std::array or

> std::span-specific CTADs?

>

IMO, no it would not.

Notice that what you have there is equivalent in effect to:

constexpr auto v = []<size_t... Is>(index_sequence<Is...>) {

size_t v[] = {Is...};

auto [begin, end] = std::ranges::remove(v, 42);

return std::inplace_vector<size_t, *sizeof...(Is)*>(v, begin);

}(std::make_index_sequence<N>{});

`*sizeof...(Is)*` is almost certainly the *wrong* capacity, there. Either

you're planning to add more elements to it (so you need more capacity), or

you're not (in which case you should use a capacity equal to the *final*

size, not the size *before* the remove).

I also have a vague sense that since inplace_vector is expensive to

move-construct, we really don't want to encourage people to create

inplace_vectors without thinking about capacity — and then have to move

them into the "proper" type later on. We can get away with that for types

like `shared_ptr<U>` and `unique_ptr<U>`, where converting to the correct

type is O(1). But for `inplace_vector<T, M>`, converting to

`inplace_vector<T, N>` is an O(M) operation.

HTH,

Arthur

Received on 2024-07-28 21:20:35