Why is there no piecewise tuple construction?
Question: Why doesn't the same piecewise constructibility exist for arrays and tuples?
My recollection is that piecewise construction was added to std::pair
for one reason only: to support uses-allocator construction of the pair elements, i.e. to allow an allocator to be provided and conditionally passed to the elements if they support construction with an allocator (see [allocator.uses] in the standard).
At one point during the C++0x process std::pair
had twice as many constructors as it does now, with every constructor having a corresponding "allocator-extended" version taking a std::allocator_arg_t
and an allocator argument e.g.
template<class T, class U>
struct pair {
pair();
pair(allocator_arg_t, const Alloc&);
template<class TT, class UU>
pair(TT&&, UU&&);
template<class Alloc, class TT, class UU>
pair(allocator_arg_t, const Alloc&, TT&&, UU&&);
// etc.
There was something of a running joke (haha, only serious) about the insane complexity of std::pair
. The support for passing allocators to the elements was removed from std::pair
and moved into std::scoped_allocator_adaptor
, which is responsible for detecting whether the elements should be constructed with an allocator (see the construct
overloads taking a pointer to std::pair
in [allocator.adaptor.members]).
A nice consequence of the piecewise construction is that you can do "emplace" style initialization of pair elements, allowing pairs of non-movable, non-copyable types, but as far as I know that was not the goal of the design.
So the reason tuple
doesn't support it is that the feature was invented to simplify pair
which had ballooned from a very simple type in C++03 to a laughing stock in C++0x, but doing the same for tuple
was not considered as important (it was new for C++11 anyway). Also, extending scoped_allocator_adaptor
to handle tuples of arbitrary numbers of elements would have made that adaptor much more complicated.
As for std::array
, that's an aggregate type (because reasons) so adding a constructor taking piecewise_construct_t
is not possible without making it a non-aggregate.
C++11 use-case for piecewise_construct of pair and tuple?
Not all types can be moved more efficiently than copied, and for some types it may make sense to even explicitly disable both copying and moving. Consider std::array<int, BIGNUM>
as an an example of the former kind of a type.
The point with the emplace
functions and piecewise_construct
is that such a class can be constructed in place, without needing to create temporary instances to be moved or copied.
struct big {
int data[100];
big(int first, int second) : data{first, second} {
// the rest of the array is presumably filled somehow as well
}
};
std::pair<big, big> pair(piecewise_construct, {1,2}, {3,4});
Compare the above to pair(big(1,2), big(3,4))
where two temporary big
objects would have to be created and then copied - and moving does not help here at all! Similarly:
std::vector<big> vec;
vec.emplace_back(1,2);
The main use case for piecewise constructing a pair is emplacing elements into a map
or an unordered_map
:
std::map<int, big> map;
map.emplace(std::piecewise_construct, /*key*/1, /*value*/{2,3});
Why do we need piecewise_construct in map emplace in C++20?
weirdo classes that take std::tuple as argument to constructor
Yes, exactly.
If you decided that m.emplace(x, y)
automatically did piecewise_construct functionality for tuples x
and y
, then it would be impossible for me to emplace construct this:
struct Key { Key(std::tuple<int, int>); bool operator<(Key const&) const; };
struct Value { Value(std::tuple<char>); };
map<Key, Value> m;
m.emplace(std::tuple(1, 2), std::tuple('3'));
I need to provide tuple
s for both types, but instead I'd always get two int
s for Key
and a char
for Value
and that doesn't work. I would necessarily have to either add an extra constructor for Key
or just not do any emplacing.
Now, if you follow up and say, okay, m.emplace(x, y)
will automatically do piecewise_construct
if x
and y
are tuples and also the piecewise construction is actually valid... then , first, that's getting fairly complicated. But also, what if both constructions are valid?
struct Key2 {
Key2(std::tuple<int, int>);
Key2(int, int);
bool operator<(Key2 const&) const;
};
map<Key2, Value> m2;
m2.emplace(std::tuple(1, 2), std::tuple('3'));
Now this always uses the (int, int)
constructor. Is that what the user intended here?
Basically, having them separate means the user can do what they need and there's no ambiguity.
Where is piecewise_construct for boost::container::map::emplace()?
piecewise_construct support for both C++03 and C++11 capable compilers was added in commit:
https://github.com/boostorg/container/commit/79a75f470e75f35f5f2a91e10fcc67d03b0a2160
and will be officially released in Boost 1.62. The following code compiles fine:
#include <boost/tuple/tuple.hpp>
#include <tuple>
#include <boost/container/map.hpp>
class A {
public:
/**/ A( int ) { }
bool operator<( const A & ) const { return false; }
} ;
class B {
public:
/**/ B( int, const char * ) { }
} ;
int main( int, char *[] )
{
A a( 100 );
B b( 200, "foo" );
boost::container::map<A,B> m;
//1) Both Boost.Tuple and std tuple supported
//2) std:: or boost::container::piecewise_construct supported
m.emplace( boost::container::piecewise_construct,
boost::make_tuple( 300 ),
boost::make_tuple( 400, "World" ) );
m.emplace( std::piecewise_construct,
std::make_tuple( 400 ),
std::make_tuple( 500, "World2" ) );
}
Initialize a tuple of non-copyable and non-movable classes
std::tuple
has few facilities for constructing its members in-situ. The generally expected way to initialize a tuple
is via copy/move from its component objects.
It does permit implicit conversion of constructor parameters to their respective arguments. But that is only a 1:1 relationship between parameters and tuple
members. There is no way to have a many-to-one relationship between constructor parameters and tuple
members.
You can allow your type to be implicitly constructible from a tuple
itself:
class A
{
public:
A(float, int) {}
A(const std::tuple<float, int> &tpl)
: A(std::get<0>(tpl), std::get<1>(tpl)) {}
private:
A() = delete;
A(const A&) = delete;
A(A&&) = delete;
};
Thus, you can construct a tuple
like this:
class B
{
public:
B() : ta(std::tuple<float, int>(0.0f, 1)) {}
private:
std::tuple<A> ta;
};
If you want to employ deeper metaprogramming, you may yet be able to make a set of constructors that allow your type to be constructed from a tuple
whose types match any available constructors:
class A
{
public:
A(float, int) {}
template<typename ...Args>
A(const std::tuple<Args...> &args) : A(args, std::index_sequence_for<Args...>{}) {}
private:
A() = delete;
A(const A&) = delete;
A(A&&) = delete;
template<typename ...Args, std::size_t ...Is>
A(const std::tuple<Args...> &args, std::index_sequence<Is...>)
: A(std::get<Is>(args)...) {}
};
Why does unordered_map's emplace with piecewise_construct argument needs default constructor?
std::unordered_map
needs default constructor is because of operator[]
. map[key]
will construct new element using default constructor if key
is not exist in map
.
You can totally use map without default constructor. E.g. following program will compile with no error.
struct A {
int x;
A(int x) : x(x) {}
};
...
std::unordered_map<int, A> b;
b.emplace(std::piecewise_construct,
std::forward_as_tuple(1),
std::forward_as_tuple(2));
b.at(1).x = 2;
How to initialize a tuple of non-default-constructible not-copyable objects?
Can you just add a piecewise constructor for your types? If so, you can create a horrible macro that unpacks and delegates a tuple:
#define CONSTRUCT_FROM_TUPLE(CLS) \
template <class... Ts> \
CLS(std::tuple<Ts...> const& tup) \
: CLS(tup, std::index_sequence_for<Ts...>{}) \
{ } \
\
template <class Tuple, size_t... Is> \
CLS(Tuple const& tup, std::index_sequence<Is...> ) \
: CLS(std::get<Is>(tup)...) \
{ }
And just add it to your types:
struct A {
A(bool, int ) { }
A(const A& ) = delete;
CONSTRUCT_FROM_TUPLE(A)
};
struct B {
B(char, double ) { }
B(const B& ) = delete;
CONSTRUCT_FROM_TUPLE(B)
};
And pass in tuples:
std::tuple<A, B> tup(
std::forward_as_tuple(true, 42),
std::forward_as_tuple('x', 3.14));
Pre-C++11, I don't know that this is possible - you don't have delegating constructors at all. You'd have to either:
- Write your own
tuple
-like class that accepts tuples in its constructor - Add tuple constructors to your types that explicitly initialize the same thing as the non-tuple versions did
- Have a tuple of types that are single-argument constructible, like
boost::tuple<boost::scoped_ptr<A>, boost::scoped_ptr<B>>(new A(...), new B(...))
(1) is a lot of work, (2) is code duplication and error prone, and (3) involves now having to do allocation all of a sudden.
Eliminate copies when constructing members of a class
This is already optimal:
Comp(T&& t, U&& u)
: t_m(std::move(t))
, u_m(std::move(u))
{ }
If T
has a move constructor, this'll be a move. If it doesn't, this'll be a copy - but then there's no way around making a copy somewhere. And it isn't copyable, then the whole question is moot.
Of course this only works for rvalues, so you'd want something for lvalues too. That unfortunately gets a little complicated:
template <class Tx, class Ux,
class = std::enable_if_t<std::is_convertible<std::decay_t<Tx>*, T*>::value &&
std::is_convertible<std::decay_t<Ux>*, U*>::value>>
Comp(Tx&& t, Ux&& u)
: t_m(std::forward<Tx>(t))
, u_m(std::forward<Ux>(u))
{ }
Here we want to allow deduction of Tx
such that it's either T
, T&
, or D
, D&
where D
derives from T
. std::decay
drops the reference and is_convertible
for pointers checks if it's derived.
Okay, can we do better? Not really. This is either going to do 1 move or 1 copy for each member. But what if we want to construct them in place? That we should be able to allow for:
template <class... TArgs, class... UArgs,
class = std::enable_if_t<std::is_constructible<T, TArgs...>::value &&
std::is_constructible<U, UArgs...>::value>>
Comp(std::piecewise_construct_t pc, std::tuple<TArgs...> const& t, std::tuple<UArgs...> const& u)
: Comp(t, u, std::index_sequence_for<TArgs...>{}, std::index_sequence_for<UArgs...>{})
{ }
private:
template <class TTuple, class UTuple, size_t... Is, size_t... Js>
Comp(TTuple const& t, UTuple const& u, std::index_sequence<Is...>, std::index_sequence<Js...> )
: t_m(std::get<Is>(t)...)
, u_m(std::get<Js>(u)...)
{ }
With this potentially we can avoid any kind of copy or move at all by just constructing in-place. Whether or not this is beneficial depends on your usage of Comp
.
Related Topics
Gcc Std::Thread Not Found in Namespace Std
Move Element from Boost Multi_Index Array
C++ Get Handle of Open Sockets of a Program
Generate All Sequences of Bits Within Hamming Distance T
Why Does (1 << 31) >> 31 Result in -1
Can You Access Private Member Variables Across Class Instances
C/C++ Inline Assembler with Instructions in String Variables
How to Run a Bash Script from C++ Program
Can't Use Structure in Global Scope
Are Lambdas Inlined Like Functions in C++
C++ Access Violation Reading Location 0Xcdcdcdcd Error on Calling a Function
Finding the Max Value in a Map
How Does the Stl's Multimap Insert Respect Orderings
Throw and Ternary Operator in C++
Combining Two Lists by Key Using Thrust
What Are the Incompatible Differences Between C(99) and C++(11)
Special Characters in Visual Studio 2019 C++ Project and Executing Cmd Commands with Them