Well today I met a werid behavior when using std::tuple()
and std::tuple{}
.
Here is an easy demo:
#include <iostream>
#include <tuple>
#define rd ({ int x = (std::cin >> x, x); x; })
template <typename... Args>
void log(const Args &...args) { ((::std::cout << args << ", "), ...); }
auto main() -> int {
auto [a, b] = std::tuple(rd, rd);
auto [c, d] = std::tuple{rd, rd};
log(a, b, c, d);
return {};
}
Run:
echo '1 2 3 4' > input
clang -std=c 17 demon.cpp
./a.out < input
g -std=c 17 demon.cpp
./a.out < input
cat demon.cpp
And I got 2 different results: 1, 2, 3, 4
and 2, 1, 3, 4
. That's out of my expectation. I turned to my friends and got the reply of "The macro rd
is a unspecific behaivor". However, I am still confused with this answer after learning something about unspecific behaivor.
CodePudding user response:
Your friends are correct.
The unspecified behaviour occurs because the evaluation order of function arguments is not specified, it can happen in any order and can change between calls. Meaning the order of std::cin
is not guaranteed. The compiler is free to reorder them as it sees fit. This applies to std::tuple()
, hence the incorrect output.
On the other hand for braced-init-list, the order is fixed left-to-right, therefore std::tuple{}
is safe and the output is guaranteed.
Furthermore, in rd
, having braced-statements in expressions is not standard C so you are at the mercy of your compiler. I would seriously recommend macro-less refactoring, hiding std::cin
in an expression seems like asking for trouble.
Why not simple read_int()
function or generic read_T
? Then you can generalize to read_Ts<T1,T2,T3...>
with explicit sequencing. For example:
template<typename T>
T read_T(){
T x;
std::cin>> x;
return x;
}
template<typename...T>
auto read_Ts(){
return std::tuple{read_T<T>()...};
}