7

The following program:

#include <iostream>
#include <tuple>

struct A {
    A() { std::cout << "A constructor\n"; }
};

struct B {
    B() { std::cout << "B constructor\n"; }
};

int main() {
    std::tuple<A, B> t;
}

gives different outputs on different compilers:

# libstdc++
B constructor
A constructor
# libc++
A constructor
B constructor

This seem weird... I figured the standard would have guaranteed the tuple elements be constructed in-order, e.g., A, B, ..., Y, Z?

Brian Rodriguez
  • 4,250
  • 1
  • 16
  • 37

2 Answers2

6

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.

Brian Rodriguez
  • 4,250
  • 1
  • 16
  • 37
2

As you've seen, the standard does not define an ordering here. I've only seen it happen in reverse order, but in principle a compiler could do anything it wanted. Worse, your request for a "standardized constructor" will not prosper, because this issue isn't specific to constructors: all function arguments work this way!

Consider this example:

bool putOnTheSpaceSuits() { /* ... */ }
bool openTheAirlock() { /* ... */ }
void tryGoIntoSpace(bool spaceSuitsOn, bool airlockOpen) {
  if(spaceSuitsOn && airlockOpen) {
    spacewalk();
  }
}

What happens when we run tryGoIntoSpace(putOnTheSpaceSuits(), openTheAirlock())? On my machine, openTheAirlock() is evaluated first, dumping our unprotected astronauts into space. Oops!

Your original question uses two implicit conversions; it's equivalent to std::tuple<X,Y> t(X(1),Y(2));. You can see the same effect with any random free function that takes an X and a Y:

void frob(X x, Y y) { /* ... */ }

frob(X(1), Y(2)); // It's unspecified, but I bet Y(2) will happen first here.

See for yourself: http://coliru.stacked-crooked.com/a/e4142f3c8342ebf2

The fact that you're using a recursively-templated tuple constructor isn't relevant here; all C++ functions are alike. Ideally your function arguments should not have interesting, mutually-interacting side effects, but if that's impossible, you have to do the ordering yourself:

X x(1);
Y y(2);
std::tuple<X,Y> t(x, y);
David Seiler
  • 9,557
  • 2
  • 31
  • 39
  • You can move `x` and `y` inside `t` to achieve the same semantics. – Quentin Aug 25 '15 at 08:53
  • In general yes, though in this case `t` is a library function. – David Seiler Aug 25 '15 at 09:00
  • 2
    BTW, it is not a problem with (undefined) function ordering call in OP code, `std::tuple(1, 2)` is not *equivalent* to `std::tuple(X(1), Y(2))` as `X`, `Y` are built in place and not moved from the temporaries. it is actually the difference between `struct tup {tup(int a, int b) : x(a), y(b) {} X x; Y y;};` and `struct tup {tup(int a, int b) : y(b), x(a) {} Y y; X x;};`. – Jarod42 Aug 25 '15 at 09:41
  • Construction and function calls are fundamentally different, you shouldn't really compare the two. We rely on a struct's promise that its elements are constructed in the order they are declared in, for example, because this gives us the ability to pass in refs to the elements to others later down the chain. Since tuples are essentially type generic structs, there is no reason for them to vary this implementation. – Brian Rodriguez Aug 25 '15 at 23:00
  • @BrianRodriguez I'm not sure what to tell you except that the standard doesn't agree. If you want to control the order in which the constructors are called, you have to move the calls into the tuple constructor, as proposed by Jarod42 or Quentin. – David Seiler Aug 26 '15 at 10:57
  • @David Well I've submitted an [issue to the standard](http://cplusplus.github.io/LWG/lwg-active.html#2528), all we can do now is wait for a fix unfortunately – Brian Rodriguez Aug 26 '15 at 11:48
  • @BrianRodriguez: And they didn't vary this implementation. The thing you want to rely on that you currently can't is that the order of declaration of the members. Think `struct tuple : private tuple { A data; };` –  Aug 26 '15 at 12:19
  • @Hurkyl There are tuple implementations that don't rely on recursion, see some of the posts [here](https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/mbD5G5mqKwE). Thus right now, it's just a matter of getting the committee to decide whether it's worth standardizing an implementation, and I've put in an argument for standardizing in-order. – Brian Rodriguez Aug 26 '15 at 12:26