Связанное внутреннее поведение ostream и его результаты на MSVC (по сравнению с Clang)

Проблема упорядочения потоков, внутренней строки и операций с MSVC по сравнению с GCC / Clang

Всем привет,

Совсем недавно я начал более серьезно работать с MSVC для моего кроссплатформенного проекта, и при тестировании результатов через связанный поток STD (т.е. последовательность obj.foo() << endl << obj.bar() << endl << [..etc]) я столкнулся с поведением при использовании внутренней обновленной строки, чего я не ожидал и не встречал в Linux с GCC или Clang.

Версии компилятора были GCC 7.5, Clang 11.0 и MSVC 14.0, все с включенным стандартом C ++ 17 (хотя и не завершенным). [изменить: та же проблема с MSVC 16.6.3 (внутренняя версия компилятора 19.26.28806.0)]

Для быстрого понимания вот упрощенная версия проблемы:

#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
}

Похоже, что со строкой или потоковой версией все операции используют один и тот же конечный измененный строковый объект, но я не могу понять, почему так (снова, без проблем с инструментами GNU / Linux).

Я мог бы добавить, что если мы отключим потоки, эта проблема с порядком исчезнет:

    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"

Сначала я подумал, что это проблема с промывкой, но тесты показали обратное. На самом деле использование endl или даже flush между каждым связанным вызовом ничего не дает.

Это может быть известное базовое поведение Visual-C ++ или даже CPP101 (в памяти и т. чертовски странно в моей книге.

Спасибо !

Изменить

Мне удалось воспроизвести проблему в GNU / Linux (с моим проектом, а не с приведенным выше кодом), иронично пытаясь найти альтернативу с помощью вариативного расширения шаблона, но вот вещи:

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
}

В MSVC поток, похоже, работает как этот вариативный шаблон пост-инкремента: результаты каким-то образом отстают (или больше похожи на рекурсивное применение пост-инкремента). Я не уверен, что это имело для меня смысл.


person Saachz    schedule 30.06.2020    source источник
comment
Если я читаю документы .microsoft.com / en-us / cpp / overview / правильно, Изменения порядка оценки в c ++ 17 не реализованы до 15.7. 14 недостаточно.   -  person user4581301    schedule 01.07.2020
comment
@ user4581301 звучит достойно ответа.   -  person Mark Ransom    schedule 01.07.2020
comment
Согласовано. Мне просто нужно было прочитать еще немного, чтобы убедиться, что я читаю правильно.   -  person user4581301    schedule 01.07.2020
comment
Я столкнулся с чем-то похожим, только что закончил обновление Visual Studio, у меня все еще та же ошибка (но спасибо).   -  person Saachz    schedule 01.07.2020
comment
После установки Visual Studio 2017 вы выполнили несколько раундов обновления, чтобы убедиться, что это не просто 15.0, 15.03 или 15.5? Вы убедились, что / std: c ++ 17 или / std: c ++ latest установлен?   -  person user4581301    schedule 01.07.2020
comment
Я почти уверен, но все трижды проверю (и обновления). Если кто-то, уверенный в своих настройках, может перепроверить предоставленный код на стороне, это было бы неплохо.   -  person Saachz    schedule 01.07.2020
comment
Ваше дополнительное редактирование отличается. В этом случае вызов println(i++,i++,i++) по-прежнему не имеет последовательности, даже в C ++ 17. Но в вашем первом случае это похоже на ошибку msvc.   -  person cigien    schedule 03.07.2020
comment
@cigien Да, это должно было указать на сходство поведения с тем, что у меня есть с потоками (даже если я подозревал, что вариативные шаблоны работают по-другому). Вероятно, это ошибка, спасибо за понимание.   -  person Saachz    schedule 05.07.2020


Ответы (1)


Согласно языку Microsoft C ++ таблица соответствия, измененные правила порядка оценки C ++ 17 не реализовано до VS 2017 15.7. 14.0 недостаточно. Придется обновлять цепочку или нет.

Тестирование

#include <iostream>


int f()
{
    static int i = 0;
    return i++;
}

int main()
{
    std::cout << f() << f();
}

Должен выдавать 01 после C ++ 17

Без включения поддержки C ++ 17 (Свойства- ›Свойства конфигурации-› Язык- ›Стандарт языка C ++ = по умолчанию) я получаю 10, функции оцениваются в обратном порядке.

С помощью Properties- ›Configuration Properties-› Language- ›C ++ Language Standard = ISO C ++ 17 Standard (/ std: c ++ 17) я получаю ожидаемое 01.

Но если я запускаю код спрашивающего ... я все равно вижу неправильный ответ. Удалив большую часть примера и добавив дополнительную строку отладки (и заменив cerr на cout, чтобы увидеть, есть ли там какая-то глубокая магия), я получаю

#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
}

и вывод

in func with 2
in func with 1
in func with 0
0
0
0
content : 0

Ясно, что его называют задом наперед. Я должен отказаться от этого и выполнить некоторую оплачиваемую работу, но либо я неправильно понимаю

  1. В выражении оператора сдвига E1<<E2 и E1>>E2 каждое вычисление значения и побочный эффект E1 упорядочиваются перед каждым вычислением значения и побочным эффектом E2

(Цитата cppreference) или происходит что-то подозрительное.

person user4581301    schedule 30.06.2020
comment
Спасибо, но обновление ничего не меняет, я соответствующим образом отредактирую свой пост. - person Saachz; 01.07.2020
comment
Смурф. @Saachz Все, что у меня есть, - это VS 2015, и могу подтвердить, что он там не работает, как и ожидалось. Создание виртуальной машины для создания сообщества 2019 года. - person user4581301; 01.07.2020
comment
Что ж, спасибо, последняя версия, похоже, все еще имеет проблему (с моей стороны). Вот хорошая версия тех же данных, кстати: github.com/MicrosoftDocs/cpp-docs/blob/master/docs/overview/ - person Saachz; 01.07.2020
comment
Я почти скачал и установил 2019 год. Тогда я могу убедиться. Проблема должна быть видна с помощью простого std::cout << i++ << i++;. Если это не так, значит, что-то еще идет не так. - person user4581301; 01.07.2020
comment
Я могу ошибаться с кляпом i++. Думаю, я должен быть прав, но g ++ все еще ругает меня, и я получаю неправильный ответ. Я мог упустить некоторую тонкость. Заменил его вызовом функции. Обновление ответа - person user4581301; 01.07.2020
comment
Спасибо за это, я только что проснулся, а это все еще ускользает от меня. Что ж, в конце концов мы разберемся с этим. - person Saachz; 01.07.2020
comment
Похоже на ошибку msvc. К сожалению, воспроизвести не могу; не могу найти онлайн-компилятор, который выполняет msvc. - person cigien; 03.07.2020