问题
An issue of streams, internal string, and operation ordering with MSVC versus GCC / Clang
Hello everyone,
I just recently began to work more seriously with MSVC for a cross-platform project of mine, and while testing outputs via chained STD stream (ie. a succession of obj.foo() << endl << obj.bar() << endl << [..etc]
) I came across a behavior when using internally updated string neither did I expected nor had encountered on Linux with GCC or Clang.
Compiler versions were GCC 7.5, Clang 11.0 and MSVC 14.0, all with c++17 standard enabled (albeit not completed). [edit: same issue using MSVC 16.6.3 (compiler internal version 19.26.28806.0) ]
For a quick understanding here a simplified version of the issue :
#include <iostream>
#include <ostream>
#include <string>
class Sample {
std::string s;
int x;
public:
Sample() = default;
friend std::ostream& operator<<(std::ostream& os, const Sample& a);
// Update internal value, return the object.
Sample const& set(std::string ss, int xx) {
s = ss;
x = xx;
return *this;
}
// Update internal value, return the string.
std::string const& setStr(std::string ss, int xx) {
set(ss, xx);
return s;
}
// Update internal value, return the int.
int const& setX(std::string ss, int xx) {
set(ss, xx);
return x;
}
};
// Output the object integer, same behavior with the string
// or if we flush inside or anything.
std::ostream& operator<<(std::ostream& os, Sample const& a)
{
os << a.x;
return os;
}
int main() {
Sample a;
// GCC / Clang | MSVC
std::cerr << a.set("0", 0) << std::endl // 0 0
<< a.set("1", 1) << std::endl // 1 0
<< a.set("2", 2) << std::endl; // 2 0
std::cerr << "content : " << a << std::endl; // 2 0
a.set("",-1); std::cerr << std::endl;
std::cerr << a.setStr("0", 0) << std::endl // 0 0
<< a.setStr("1", 1) << std::endl // 1 0
<< a.setStr("2", 2) << std::endl; // 2 0
std::cerr << "content : " << a << std::endl; // 2 0
a.set("",-1); std::cerr << std::endl;
std::cerr << a.setX("0", 0) << std::endl // 0 0
<< a.setX("1", 1) << std::endl // 1 1
<< a.setX("2", 2) << std::endl; // 2 2
std::cerr << "content : " << a << std::endl; // 2 2
}
It appears that with the string or streamed out version all operations use the same final mutated string object, but I can't figure why so (again, with no problem on GNU / Linux toolchains).
I might add that if we unchain the streams this ordering problem disappear :
std::cerr << a.set("0", 0) << std::endl; // "0"
std::cerr << a.set("1", 1) << std::endl; // "1"
std::cerr << a.set("2", 2) << std::endl; // "2"
I first thought it was a flushing problem, but tests shown otherwise. Actually using endl
or even flush
in between each chained call does nothing.
It might be a Visual-C++ or even CPP101 known basic behavior (on memory and whatnot) but I had found nothing about it, so I'll be greatly grateful for any advice you could have as it's pretty darn strange in my book.
Thanks !
Edit
I have been able to reproduce the problem on GNU / Linux (with my project, not the above code) ironically trying to find an alternative via template variadic expansion, but here the things :
void println() // base function
{
std::cerr << std::endl;
}
template<typename T, typename... Ts>
constexpr void println(T head, Ts... tail)
{
std::cerr << head << std::endl;
println(tail...);
}
int main()
{
int i;
i = 0;
println(++i, ++i, ++i); // 3 3 3
i = 0;
println(i++, i++, i++); // 2 1 0
}
On MSVC the stream seem to work like this post-increment variadic template : the results are somehow backward (or more like post recursively applied). I am not sure it made sense to me.
回答1:
According to the Microsoft C++ language conformance table, C++17's changed evaluation order rules were not implemented until VS 2017 15.7. 14.0 is not good enough. You will have to upgrade or not chain.
Testing
#include <iostream>
int f()
{
static int i = 0;
return i++;
}
int main()
{
std::cout << f() << f();
}
Should produce 01 after C++17
Without turning on C++17 support (Properties->Configuration Properties->Language->C++ Language Standard = default) I get 10, the functions are evaluated in reverse.
With Properties->Configuration Properties->Language->C++ Language Standard = ISO C++17 Standard (/std:c++17) I get the expected 01.
But if I run the asker's code... I still see the incorrect response. Removing most of the example and adding in an extra debug line (and replacing cerr
with cout
to see if there's some deep magic there) I get
#include <iostream>
#include <ostream>
#include <string>
class Sample {
std::string s;
int x = 0;
public:
Sample() = default;
friend std::ostream& operator<<(std::ostream& os, const Sample& a);
// Update internal value, return the object.
Sample const& set(std::string ss, int xx) {
std::cout << "in func with " << ss << std::endl;
s = ss;
x = xx;
return *this;
}
};
// Output the object integer, same behavior with the string
// or if we flush inside or anything.
std::ostream& operator<<(std::ostream& os, Sample const& a)
{
os << a.x;
return os;
}
int main() {
Sample a;
// GCC / Clang | MSVC
std::cout << a.set("0", 0) << std::endl // 0 0
<< a.set("1", 1) << std::endl // 1 0
<< a.set("2", 2) << std::endl; // 2 0
std::cout << "content : " << a << std::endl; // 2 0
}
and output
in func with 2 in func with 1 in func with 0 0 0 0 content : 0
Clearly being called backwards. I have to ditch this and get some paid work done, but either I'm misreading
- In a shift operator expression
E1<<E2
andE1>>E2
, every value computation and side-effect ofE1
is sequenced before every value computation and side effect ofE2
(Quoting cppreference) or there's something fishy going on.
来源:https://stackoverflow.com/questions/62666619/chained-ostream-internal-behavior-and-their-results-on-msvc-versus-clang