std::vector (and other containers) to have a perfect forwarding constructor

473 views
Skip to first unread message

len...@gmail.com

unread,
Nov 11, 2015, 3:52:01 AM11/11/15
to ISO C++ Standard - Future Proposals
Hi,

I didn't find a topic like this, so here it is. Something like this would be nice:

template <class... Args>
vector
(size_type n,Args&&... args);

It would perfect forward args to value_type's constructor. Since vector uses its allocator to construct, and in C++11 std::allocator already has a perfect forwarding "construct" member function, it could use that. The only problem I see that it would hijack other constructors so maybe it would need tag dispatching to work.

vector(size_type n,vector::perfect_forward_type,Args&&... args);

I see that this is a bit uglier, but at least it would work.

Use cases:

1) It could be faster than vector(n,value_type(args)) if value_type's copy constructor is more expensive than its constructor with "args" parameters (I don't know a good example though).
2) If value_type's different constructors are not deterministic then maybe it's desirable to call the constructor multiple times instead of copying one constructed element. One example if value_type has a constructor taking a random generator as an argument.

Workarounds:
1) Using a custom allocator. Problem: your allocator type is now stuck with your vector instance. Maybe one would initialize the vector with a custom allocator but wouldn't mind if std::allocator would be used in the future uses of the same vector.
2) Construct an empty vector, use reserve then emplace_back newly constructed elements one by one using value_type's constructor of choice. I'm not sure if this would be equally efficient.

Best regards,
Lénárd Szolnoki



Daniel Krügler

unread,
Nov 11, 2015, 4:01:56 AM11/11/15
to std-pr...@isocpp.org
2015-11-11 9:52 GMT+01:00 <len...@gmail.com>:
> Hi,
>
> I didn't find a topic like this, so here it is. Something like this would be
> nice:
>
> template <class... Args>
> vector(size_type n,Args&&... args);
>
> It would perfect forward args to value_type's constructor. Since vector uses
> its allocator to construct, and in C++11 std::allocator already has a
> perfect forwarding "construct" member function, it could use that. The only
> problem I see that it would hijack other constructors so maybe it would need
> tag dispatching to work.
>
> vector(size_type n,vector::perfect_forward_type,Args&&... args);
>
> I see that this is a bit uglier, but at least it would work.

I consider the combination of "perfect forwarding" and the additional
size information as a potential dangerous combination. If a
constructor with size information would get any rvalues via args, the
first vector element construction might move the arguments into the
target and the second, third, etc. vector element constructed from the
same argument pack would try to become constructed from already moved
arguments. For exactly this reason any existing container member
function that provides a size information and a "prototype" value
expects that value as const value_type& and not as a potentially
movable argument.

- Daniel

len...@gmail.com

unread,
Nov 11, 2015, 4:20:01 AM11/11/15
to ISO C++ Standard - Future Proposals

On Wednesday, November 11, 2015 at 10:01:56 AM UTC+1, Daniel Krügler wrote:
I consider the combination of "perfect forwarding" and the additional
size information as a potential dangerous combination. If a
constructor with size information would get any rvalues via args, the
first vector element construction might move the arguments into the
target and the second, third, etc. vector element constructed from the
same argument pack would try to become constructed from already moved
arguments. For exactly this reason any existing container member
function that provides a size information and a "prototype" value
expects that value as const value_type& and not as a potentially
movable argument.

- Daniel

Yeah, that was truly a mistake by me, perfect forwarding really doesn't make sense in this context. Shouldn't this work though?

vector(size_type n,vector::forward_args_type,Args&... args);

 
This way even if something in args is an rvalue reference its type would be deduced to be an lvalue reference.

- Lénárd

Daniel Krügler

unread,
Nov 11, 2015, 4:33:49 AM11/11/15
to std-pr...@isocpp.org
2015-11-11 10:20 GMT+01:00 <len...@gmail.com>:
> Yeah, that was truly a mistake by me, perfect forwarding really doesn't make
> sense in this context. Shouldn't this work though?
>
> vector(size_type n,vector::forward_args_type,Args&... args);
>
> This way even if something in args is an rvalue reference its type would be
> deduced to be an lvalue reference.

I would say the arguments should also be declared as references to
const, because you can also have the same effect for mutable lvalues.
And surely you would like to construct from a pack from literals
(which are rvalues in basically all cases), which would not be
possible with your revised form:

std::vector<Foo> v(100, 12.123); // Wouldn't work

- Daniel

len...@gmail.com

unread,
Nov 11, 2015, 5:53:16 AM11/11/15
to ISO C++ Standard - Future Proposals
Ok, this is my last try :).

What if we use the first declaration:

vector(size_type n,vector::forward_args_type,Args&&... args);

But inside vector's constructor we convert every rvalue references in args to const ref. Of course this behavior should be well documented and I maybe this can be confusing.

About converting rvalue references one can define the following type:

template<typename T>
using forward_for_copy=std::conditional_t< std::is_rvalue_reference<T>{},
                                           const T&,
                                           T >;

Can be used this way:
template<class... Args>
int f(Args&&... args)
{
        h(static_cast<forward_for_copy<Args>>(args)...);
        return 0;
}

(Maybe "forward for copy" is not the best name though.) The main idea that vector's constructor would forward args this way to std::allocator's "construct" member function. The constructor would accept literals, but it wouldn't move objects even if they are movable.

I'm getting a little bit lost here though, maybe this suggestion adds even more problems than what it solves.

- Lénárd

len...@gmail.com

unread,
Nov 11, 2015, 6:52:37 AM11/11/15
to ISO C++ Standard - Future Proposals
Oh, also about mutable lvalues. I see no reason to not accept them especially if value_type's constructor accepts them (like passing a random engine, which is mutable).

Matthew Woehlke

unread,
Nov 11, 2015, 9:36:38 AM11/11/15
to std-pr...@isocpp.org
On 2015-11-11 03:52, len...@gmail.com wrote:
> I didn't find a topic like this, so here it is. Something like this would
> be nice:
>
> template <class... Args>
> vector(size_type n,Args&&... args);
>
> 1) It could be faster than vector(n,value_type(args)) if value_type's copy
> constructor is more expensive than its constructor with "args" parameters
> (I don't know a good example though).

Interesting idea. And since vector(n, value_type(...)) invokes
value_type's copy ctor, it is in fact a subset of the proposed syntax,
i.e. that form could be deprecated / removed in favor of the proposed
"emplace" version.

> 2) If value_type's different constructors are not deterministic then maybe
> it's desirable to call the constructor multiple times instead of copying
> one constructed element. One example if value_type has a constructor taking
> a random generator as an argument.

Hmm... for this case, you are thinking that one of the arguments is e.g.
a functor? And that the value_type copy ctor copies existing data, while
the ctor taking the functor uses the functor (which isn't stored) to
generate a value? Interesting notion...

> Workarounds:
> 2) Construct an empty vector, use reserve then emplace_back newly
> constructed elements one by one using value_type's constructor of choice.
> I'm not sure if this would be equally efficient.

Seems like it should be at least comparably efficient; I can't think
offhand what vector's sized constructor would do differently that would
make it more efficient. (Assuming that a default-constructed data has
SOO or otherwise doesn't allocate storage that gets thrown out by the
reserve().)

--
Matthew

len...@gmail.com

unread,
Nov 11, 2015, 12:09:44 PM11/11/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com


On Wednesday, November 11, 2015 at 3:36:38 PM UTC+1, Matthew Woehlke wrote:
On 2015-11-11 03:52, len...@gmail.com wrote:
> I didn't find a topic like this, so here it is. Something like this would
> be nice:
>
> template <class... Args>
> vector(size_type n,Args&&... args);
>
> 1) It could be faster than vector(n,value_type(args)) if value_type's copy
> constructor is more expensive than its constructor with "args" parameters
> (I don't know a good example though).

Interesting idea. And since vector(n, value_type(...)) invokes
value_type's copy ctor, it is in fact a subset of the proposed syntax,
i.e. that form could be deprecated / removed in favor of the proposed
"emplace" version.

I don't think it's possible because of the optional "const allocator_type&" argument. I don't know if it's explicitly forbidden or not, but my hunch is that optional and variadic parameters don't mix well. 

I think I made mistake in the usage of my "forward_for_copy" type trait, one can use the following function instead:
template<typename T>
constexpr
decltype(auto)
forward_for_copy(std::remove_reference_t<T>& x) noexcept
{
        using ReturnType=std::conditional_t<
                std::is_rvalue_reference<T&&>{},
                const T&, 
                T &>; 
        return static_cast<ReturnType>(x);
}

Use similarly to "std::forward". What it does:
  1. Leaves lvalue references alone.
  2. Static casts all rvalue references to const lvalue references.
- Lénárd

Nevin Liber

unread,
Nov 11, 2015, 12:42:29 PM11/11/15
to std-pr...@isocpp.org
On 11 November 2015 at 04:53, <len...@gmail.com> wrote:
Ok, this is my last try :).

What if we use the first declaration:

vector(size_type n,vector::forward_args_type,Args&&... args);

But inside vector's constructor we convert every rvalue references in args to const ref.

But then it is no longer perfect forwarding, is it?  Worse, the signature looks like it should do perfect forwarding.

For instance, if you have move-only parameters that get moved into the object being constructed, it looks like it ought to work even though it won't compile.  Take this example:

unique_ptr<int> p;
vector<unique_ptr<int>> v(1, forward_args_type(), std::move(p));
 
I'm getting a little bit lost here though, maybe this suggestion adds even more problems than what it solves.

I think the idea was certainly worth exploring, but I'm leaning towards the same conclusion.
--
 Nevin ":-)" Liber  <mailto:ne...@cpluscplusguy.com(847) 691-1404
Message has been deleted

len...@gmail.com

unread,
Nov 12, 2015, 2:35:16 AM11/12/15
to ISO C++ Standard - Future Proposals


On Wednesday, November 11, 2015 at 6:42:29 PM UTC+1, Nevin ":-)" Liber wrote:
On 11 November 2015 at 04:53, <len...@gmail.com> wrote:
Ok, this is my last try :).

What if we use the first declaration:

vector(size_type n,vector::forward_args_type,Args&&... args);

But inside vector's constructor we convert every rvalue references in args to const ref.

But then it is no longer perfect forwarding, is it?
 
I agree. 

Worse, the signature looks like it should do perfect forwarding.
 
I'm not familiar with the conventions of modern C++. Is it already settled that if you see a universal reference in a signature it should be moved or perfect forwarded (so eventually moved)? AFAIK rvalue references are only candidates for moving. Anyway this convention (if it exist) seems too limiting. I only use a variadic universal reference because AFAIK it's the only way to catch mixed const and non-const lvalue references and keep their "constness" in a variadic parameter.


For instance, if you have move-only parameters that get moved into the object being constructed, it looks like it ought to work even though it won't compile.  Take this example:

unique_ptr<int> p;
vector<unique_ptr<int>> v(1, forward_args_type(), std::move(p));
 
Well not compiling is not that bad. Maybe a static assert is possible with useful information. 

 
I'm getting a little bit lost here though, maybe this suggestion adds even more problems than what it solves.

I think the idea was certainly worth exploring, but I'm leaning towards the same conclusion.
 
I worry more about more convoluted corner cases. I think this case is not that bad.

- Lénárd

len...@gmail.com

unread,
Nov 12, 2015, 4:14:30 AM11/12/15
to ISO C++ Standard - Future Proposals, len...@gmail.com
Here is my implementation for this type of constructor:


I inherit myvector from std::vector (including constructors) and only implement the new constructor, including the static_assert for move-only arguments.
Reply all
Reply to author
Forward
0 new messages