### drdilyor's blog

By drdilyor, history, 4 months ago, Take a look at this innocent piece of code:

    vector<vector<int>> arr{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 16},
};
int n = arr.size();

for (int i = 0; i < n/2; i++) {
for (int j = 0; j < n/2; j++) {
tie(arr[i][j], arr[j][n-i-1], arr[n-i-1][n-j-1], arr[n-j-1][i]) =
{arr[j][n-i-1], arr[n-i-1][n-j-1], arr[n-j-1][i], arr[i][j]};
//tie(arr[i][j], arr[j][n-i-1], arr[n-i-1][n-j-1], arr[n-j-1][i]) =
//    tuple{arr[j][n-i-1], arr[n-i-1][n-j-1], arr[n-j-1][i], arr[i][j]};
}
}
for (auto& i : arr) {
for (int j : i)
cout << j << ' ';
cout << '\n';
}



Outputs (with optimizations disabled):

4 8 12 16
3 7 11 15
8 7 10 14
4 3 9 13


which is totally gibberish.

Using tuple{} instead of initializer list ({}) for tie() gives the correct answer. This behavior is observed both on GCC and clang.

In fact, this is present with code as simple as this:

int a = 1, b = 2;
tie(a, b) = {b, a};


This once again shows one of the dark sides of C++. Hope no one spends 30 mins searching for such bug after this blog :):  Comments (6)
 » omg same on my laptop why
 » Tuple creates a copy of the value, and the initializer list will be used to create a temporary object of type std::tuple, same as the left hand side, as this is the best overload. Therefore it's the same as writing out four assignments, which is pretty much guaranteed to produce incorrect results. I'm not sure whether the order of assignments is well defined.
•  » » I visualized this behavior with a custom class: Wrapperclass Wrapper { public: int value; Wrapper(int value) { this->value = value; cout << "default-construct: " << this->value << ".\n"; } Wrapper(Wrapper const &wrapper) { this->value = wrapper.value; cout << "copy-construct: " << this->value << ".\n"; } Wrapper &operator=(Wrapper const &right) { cout << "copy-assign: " << this->value << " <- " << right.value << ".\n"; this->value = right.value; return *this; } Wrapper &operator=(Wrapper &&right) { cout << "move-assign: " << this->value << " <- " << right.value << ".\n"; this->value = right.value; return *this; } }; Then, with the initializer-list option array X{0, 1, 2}; tie(X, X, X) = {X, X, X}; we will see default-construct: 0. default-construct: 1. default-construct: 2. copy-assign: 2 <- 0. copy-assign: 1 <- 1. copy-assign: 0 <- 0. which obviously gives wrong results. With the tuple method: array X{0, 1, 2}; tie(X, X, X) = tuple{X, X, X}; we see default-construct: 0. default-construct: 1. default-construct: 2. copy-construct: 2. copy-construct: 1. copy-construct: 0. move-assign: 2 <- 0. move-assign: 1 <- 1. move-assign: 0 <- 2. which must be correct.I also read at the end of this thread that tuple assignment ordering may be left up to the compiler to allow for earlier assignment of throwable types, but I failed to recreate this on any compilers I tried. Still, it may be good to assume that ordering is not guaranteed.Here is what I tried: Throwable & Noexcept classesclass Throwable { public: Throwable() {} Throwable &operator=(Throwable const &) { cout << "[Throwable] copy-assign.\n"; throw runtime_error(""); return *this; } }; class Noexcept { public: Noexcept() noexcept {} Noexcept &operator=(Noexcept const &) noexcept { cout << "[Noexcept] copy-assign.\n"; return *this; } };  Throwable A; Noexcept B; try { tie(B, A) = tuple{B, A}; } catch (...) { cout << "Thrown & caught.\n"; } try { tie(A, B) = tuple{A, B}; } catch (...) { cout << "Thrown & caught.\n"; } But on all compilers I had access to, I could only get the result [Noexcept] copy-assign. [Throwable] copy-assign. Thrown & caught. [Throwable] copy-assign. Thrown & caught. which is inconclusive.
 » Hope no one spends 30 mins searching for such bug after this blogwho ever code like this!? (except pythonists)
•  » » 4 months ago, # ^ | ← Rev. 3 →   Well, yeah you got me, I'm a pythonista undercover :)BTW I found a prettier (=hard to understand) method: arr[i][j] = exchange(arr[j][n-i-1], exchange(arr[n-i-1][n-j-1], exchange(arr[n-j-1][i], arr[i][j]))); 
•  » » » I think this is what you're looking for #include using namespace std; template void move_left(H& first, T&... other) { tie(first, other...) = tuple{other..., first}; } int main() { int x = 1, y = 2, z = 3; move_left(x, y, z); cout << x << ' ' << y << ' ' << z << '\n'; }