C++ std::tuple order of destruction
The standard doesn't specify the order of destruction for std::tuple
. The fact that §20.4.1/p1 specifies that:
An instantiation of tuple with two arguments is similar to an
instantiation of pair with the same two arguments.
Similar here is not interpreted as identical and consequently it's not implied that std::tuple
should have a reverse destruction order of its arguments.
Given the recursive nature of std::tuple
most probable is that the order of destruction is in order with the order of its arguments.
I also base my assumptions on a bug report for GCC BUG 66699 where in the discussion my assumptions above are justified.
That said, the order of destruction for std::tuple
is unspecified.
c++ structured bindings: What is the standard order of destruction?
Structured binding changes absolutely nothing about the language in terms of order of construction/destruction. Structured binding is a fiction, a linguistic shorthand that turns get<I>(unnamed_object)
or unnamed_object.some_name
into some_name
. In this fiction, unnamed_object
is the actual object which is generated by the expression and captured by the auto[]
structured binding declaration.
That object works exactly like any other C++ object. The order of subobject declarations in the class (if it is a class) determines the order of construction and destruction, just like any other C++ object.
Now, when it comes to std::tuple
, the order of its subobject declaration is unspecified; it is allowed to vary from implementation to implementation. Again, structured binding has nothing to do with that. So the order of destruction can be whatever your particular implementation wants it to be.
Tuple isn't being constructed in order?
std::tuple
construction order is currently unspecified.
A proposal for a concrete decision on its order has been submitted to the committee but until then the order should not be relied on.
Why does libstdc++ store std::tuple elements in reverse order?
See this answer for why libc++ chose forward order. As for why libstdc++ chose reverse order, that is probably because that's how it was demonstrated in the variadics template proposal, and is the more obvious implementation.
Bonus: No. These orderings have been stable in both libraries.
Update
libc++ chose forward storage order because:
- It is implementable.
- The implementation has good compile-time performance.
- It gives clients of libc++ something that is intuitive and controllable, should they care about the order of the storage, and are willing to depend on it while using libc++, despite its being unspecified.
In short, the implementor of the libc++ tuple
merely felt that storing the objects in the order the client (implicitly) specified was the quality thing to do.
Support for std::tuple in swig?
Our goal is to make something like the following SWIG interface work intuitively:
%module test
%include "std_tuple.i"
%std_tuple(TupleDD, double, double);
%inline %{
std::tuple<double, double> func() {
return std::make_tuple(0.0, 1.0);
}
%}
We want to use this within Python in the following way:
import test
r=test.func()
print(r)
print(dir(r))
r[1]=1234
for x in r:
print(x)
i.e. indexing and iteration should just work.
By re-using some of the pre-processor tricks I used to wrap std::function
(which were themselves originally from another answer here on SO) we can define a neat macro that "just wraps" std::tuple
for us. Although this answer is Python specific it should in practice be fairly simple to adapt for most other languages too. I'll post my std_tuple.i file, first and then annotate/explain it after:
// [1]
%{
#include <tuple>
#include <utility>
%}
// [2]
#define make_getter(pos, type) const type& get##pos() const { return std::get<pos>(*$self); }
#define make_setter(pos, type) void set##pos(const type& val) { std::get<pos>(*$self) = val; }
#define make_ctorargN(pos, type) , type v##pos
#define make_ctorarg(first, ...) const first& v0 FOR_EACH(make_ctorargN, __VA_ARGS__)
// [3]
#define FE_0(...)
#define FE_1(action,a1) action(0,a1)
#define FE_2(action,a1,a2) action(0,a1) action(1,a2)
#define FE_3(action,a1,a2,a3) action(0,a1) action(1,a2) action(2,a3)
#define FE_4(action,a1,a2,a3,a4) action(0,a1) action(1,a2) action(2,a3) action(3,a4)
#define FE_5(action,a1,a2,a3,a4,a5) action(0,a1) action(1,a2) action(2,a3) action(3,a4) action(4,a5)
#define GET_MACRO(_1,_2,_3,_4,_5,NAME,...) NAME
%define FOR_EACH(action,...)
GET_MACRO(__VA_ARGS__, FE_5, FE_4, FE_3, FE_2, FE_1, FE_0)(action,__VA_ARGS__)
%enddef
// [4]
%define %std_tuple(Name, ...)
%rename(Name) std::tuple<__VA_ARGS__>;
namespace std {
struct tuple<__VA_ARGS__> {
// [5]
tuple(make_ctorarg(__VA_ARGS__));
%extend {
// [6]
FOR_EACH(make_getter, __VA_ARGS__)
FOR_EACH(make_setter, __VA_ARGS__)
size_t __len__() const { return std::tuple_size<std::decay_t<decltype(*$self)>>{}; }
%pythoncode %{
# [7]
def __getitem__(self, n):
if n >= len(self): raise IndexError()
return getattr(self, 'get%d' % n)()
def __setitem__(self, n, val):
if n >= len(self): raise IndexError()
getattr(self, 'set%d' % n)(val)
%}
}
};
}
%enddef
- This is just the extra includes we need for our macro to work
- These apply to each of the type arguments we supply to our
%std_tuple
macro invocation, we need to be careful with commas here to keep the syntax correct. - This is the mechanics of our
FOR_EACH
macro, which invokes each action per argument in our variadic macro argument list - Finally the definition of
%std_tuple
can begin. Essentially this is manually doing the work of%template
for each specialisation ofstd::tuple
we care to name inside of the std namespace. - We use our macro for each magic to declare a constructor with arguments for each element of the correct type. The actual implementation here is the default one from the C++ library which is exactly what we need/want though.
- We use our
FOR_EACH
macro twice to make a member functionget0
,get1
,getN
of the correct type of each tuple element and the correct number of them for the template argument size. Likewise forsetN
. Doing it this way allows the usual SWIG typemaps fordouble
, etc. or whatever types your tuple contains to be applied automatically and correctly for each call tostd::get<N>
. These are really just an implementation detail, not intended to be part of the public interface, but exposing them makes no real odds. - Finally we need an implementation of
__getitem__
and a corresponding__setitem__
. These simply look up and call the rightgetN
/setN
function on the class and call that instead. We take care to raiseIndexError
instead of the default exception if an invalid index is used as this will stop iteration correctly when we try to iterate of the tuple.
This is then sufficient that we can run our target code and get the following output:
$ swig3.0 -python -c++ -Wall test.i && g++ -shared -o _test.so test_wrap.cxx -I/usr/include/python3.7 -m32 && python3.7 run.py
<test.TupleDD; proxy of <Swig Object of type 'std::tuple< double,double > *' at 0xf766a260> >
['__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__swig_destroy__', '__swig_getmethods__', '__swig_setmethods__', '__weakref__', 'get0', 'get1', 'set0', 'set1', 'this']
0.0
1234.0
Generally this should work as you'd hope in most input/output situations in Python.
There are a few improvements we could look to make:
- Implement repr
- Implement slicing so that
tuple[n:m]
type indexing works - Handle unpacking like Python tuples.
- Maybe do some more automatic conversions for compatible types?
- Avoid calling
__len__
for every get/setitem call, either by caching the value in the class itself, or postponing it until the method lookup fails?
Does std::apply provide a guarantee for order of evaluation?
I have a case where it matters that the function is first applied to the first element of the tuple, then the second, then the third, ....
Then you're using the wrong function, because std::apply
does something completely unrelated to what you want. For example,
std::apply(f, std::make_tuple(1, 2))
returns
f(1, 2)
rather than trying to call f(1)
and f(2)
separately.
std::tuple for non-copyable and non-movable object
This is bad:
auto q = std::tuple<A&&,A&&>(A{100},A{200});
you are constructing a tuple
of rvalue references to temporaries that get destroyed at the end of the expression, so you're left with dangling references.
The correct statement would be:
std::tuple<A, A> q(100, 200);
However, until quite recently, the above was not supported by the standard. In N4296, the wording around the relevant constructor for tuple
is [tuple.cnstr]:
template <class... UTypes>
constexpr explicit tuple(UTypes&&... u);
Requires:
sizeof...(Types) == sizeof...(UTypes)
.is_constructible<Ti, Ui&&>::value
is true
for alli
.
Effects: Initializes the elements in the tuple with the corresponding value instd::forward<UTypes>(u)
.
Remark: This constructor shall not participate in overload resolution unless each type inUTypes
is
implicitly convertible to its corresponding type inTypes
.
So, this constructor was not participating in overload resolution because int
is not implicitly convertible to A
. This has been resolved by the adoption of Improving pair
and tuple
, which addressed precisely your use-case:
struct D { D(int); D(const D&) = delete; };
std::tuple<D> td(12); // Error
The new wording for this constructor is, from N4527:
Remarks: This constructor shall not participate in overload resolution unless
sizeof...(Types) >= 1
andis_constructible<Ti, Ui&&>::value
is true for alli
. The constructor is explicit if and only
ifis_convertible<Ui&&, Ti>::value
isfalse
for at least one i.
And is_constructible<A, int&&>::value
is true.
To present the difference another way, here is an extremely stripped down tuple implementation:
struct D { D(int ) {} D(const D& ) = delete; };
template <typename T>
struct Tuple {
Tuple(const T& t)
: T(t)
{ }
template <typename U,
#ifdef USE_OLD_RULES
typename = std::enable_if_t<std::is_convertible<U, T>::value>
#else
typename = std::enable_if_t<std::is_constructible<T, U&&>::value>
#endif
>
Tuple(U&& u)
: t(std::forward<U>(u))
{ }
T t;
};
int main()
{
Tuple<D> t(12);
}
If USE_OLD_RULES
is defined, the first constructor is the only viable constructor and hence the code will not compile since D
is noncopyable. Otherwise, the second constructor is the best viable candidate and that one is well-formed.
The adoption was recent enough that neither gcc 5.2 nor clang 3.6 actually will compile this example yet. So you will either need a newer compiler than that (gcc 6.0 works) or come up with a different design.
Why are constructed objects, in tuple initialization, copied?
It isn't copied it is still moved.
In your case, you're creating a temporary (MoveMe{10}
) that is then used to move construct the instance of MoveMe
in the tuple. After the instance of MoveMe
in the tuple is move constructed, the temporary that was moved is destroyed.
You can directly construct the MoveMe
object in the tuple by forwarding the arguments
std::cout << "Directly Constructed" << std::endl;
auto tuple = std::tuple<MoveMe>(10);
Which will not result in a temporary being created then destroyed.
nested std::forward_as_tuple and segmentation fault
The problem here is the statement "If temporaries are supposed to live as long as there are references to them." This is true only in limited circumstances, your program isn't a demonstration of one of those cases. You are storing a tuple containing references to temporaries that are destroyed at the end of the full expression. This program demonstrates it very clearly (Live code at Coliru):
struct foo {
int value;
foo(int v) : value(v) {
std::cout << "foo(" << value << ")\n" << std::flush;
}
~foo() {
std::cout << "~foo(" << value << ")\n" << std::flush;
}
foo(const foo&) = delete;
foo& operator = (const foo&) = delete;
friend std::ostream& operator << (std::ostream& os,
const foo& f) {
os << f.value;
return os;
}
};
template <typename A, typename B>
struct node { A a; B b; };
template <typename... A>
node <A&&...> make(A&&... a)
{
return node <A&&...>{std::forward <A>(a)...};
}
template <typename N>
auto fst(N&& n)
-> decltype((std::forward <N>(n).a))
{ return std::forward <N>(n).a; }
template <typename N>
auto snd(N&& n)
-> decltype((std::forward <N>(n).b))
{ return std::forward <N>(n).b; }
int main() {
using namespace std;
// A: works fine (prints '2')
cout << fst(snd(make(foo(3), make(foo(2), foo(0))))) << endl;
// B: fine in Clang, segmentation fault in GCC with -Os
auto z = make(foo(3), make(foo(2), foo(0)));
cout << "referencing: " << flush;
cout << fst(snd(z)) << endl;
}
A
works fine because it accesses the references stored in the tuple in the same full expression, B
has undefined behavior since it stores the tuple and accesses the references later. Note that although it may not crash when compiled with clang, it's clearly undefined behavior nonetheless due to accessing an object after the end of its lifetime.
If you want to make this usage safe, you can quite easily alter the program to store references to lvalues, but move rvalues into the tuple itself (Live demo at Coliru):
template <typename... A>
node<A...> make(A&&... a)
{
return node<A...>{std::forward <A>(a)...};
}
Replacing node<A&&...>
with node<A...>
is the trick: since A
is a universal reference, the actual type of A
will be an lvalue reference for lvalue arguments, and a non-reference type for rvalue arguments. The reference collapsing rules work in our favor for this usage as well as for perfect forwarding.
EDIT: As for why the temporaries in this scenario don't have their lifetimes extended to the lifetime of the references, we have to look at C++11 12.2 Temporary Objects [class.temporary] paragraph 4:
There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array. If the constructor has one or more default arguments, the destruction of every temporary created in a default argument is sequenced before the construction of the next array element, if any.
and the much more involved paragraph 5:
The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:
A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the
constructor exits.A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of
the full-expression containing the call.The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not
extended; the temporary is destroyed at the end of the full-expression in the return statement.A temporary bound to a reference in a new-initializer (5.3.4) persists until the completion of the full-expression containing the new-initializer. [ Example:
struct S { int mi; const std::pair<int,int>& mp; };
S a { 1, {2,3} };
S* p = new S{ 1, {2,3} }; // Creates dangling reference
—end example ] [ Note: This may introduce a dangling reference, and implementations are encouraged to issue a warning in such a case. —end note ]
The destruction of a temporary whose lifetime is not extended by being bound to a reference is sequenced before the destruction of every temporary which is constructed earlier in the same full-expression. If the lifetime of two or more temporaries to which references are bound ends at the same point, these temporaries are destroyed at that point in the reverse order of the completion of their construction. In addition, the destruction of temporaries bound to references shall take into account the ordering of destruction of objects
with static, thread, or automatic storage duration (3.7.1, 3.7.2, 3.7.3); that is, ifobj1
is an object with the same storage duration as the temporary and created before the temporary is created the temporary shall be destroyed beforeobj1
is destroyed; ifobj2
is an object with the same storage duration as the temporary and created after the temporary is created the temporary shall be destroyed afterobj2
is destroyed. [ Example:struct S {
S();
S(int);
friend S operator+(const S&, const S&);
~S();
};
S obj1;
const S& cr = S(16)+S(23);
S obj2;
the expression
S(16) + S(23)
creates three temporaries: a first temporaryT1
to hold the result of the expressionS(16)
, a second temporaryT2
to hold the result of the expression S(23), and a third temporaryT3
to hold the result of the addition of these two expressions. The temporaryT3
is then bound to the referencecr
. It is unspecified whetherT1
orT2
is created first. On an implementation whereT1
is created beforeT2
, it is guaranteed thatT2
is destroyed beforeT1
. The temporariesT1
andT2
are bound to the reference parameters ofoperator+
; these temporaries are destroyed at the end of the full-expression containing the call tooperator+
. The temporaryT3
bound to the referencecr
is destroyed at the end ofcr
’s lifetime,
that is, at the end of the program. In addition, the order in whichT3
is destroyed takes into account the destruction order of other objects with static storage duration. That is, becauseobj1
is constructed beforeT3
, andT3
is constructed beforeobj2
, it is guaranteed thatobj2
is destroyed before T3, and thatT3
is destroyed beforeobj1
. —end example ]
You are binding a temporary "to a reference member in a constructor's ctor-initializer".
Related Topics
How to Initialize an Array of Struct in C++
How to Assign a Value to the Pointer 'This' in C++
Is Constexpr a "Hint" (Like Inline) or "A Binding Request" to the Compiler
Unit Test That a Class Is Non Copyable, and Other Compile-Time Properties
Video Processing with Opencv in iOS Swift Project
C++ Dynamic Array Initialization with Declaration
Stl Container with Std::Unique_Ptr's VS Boost::Ptr_Container
Partial Specialization of Function Templates
What Happens If Main() Does Not Return an Int Value
C++ Template Function Default Value
Cmake Imported Library Behaviour
Stl Algorithms: Why No Additional Interface for Containers (Additional to Iterator Pairs)
Vector of Class Without Default Constructor
How Does the Friend Keyword (Class/Function) Break Encapsulation in C++
What Does *& Mean in a Function Parameter