Chained ostream internal behavior and their results on MSVC (versus Clang)

久未见 提交于 2020-07-09 05:18:16

问题


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

  1. In a shift operator expression E1<<E2 and E1>>E2, every value computation and side-effect of E1 is sequenced before every value computation and side effect of E2

(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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!