Почему std :: ostream не компилируется при использовании в тернарном операторе? - PullRequest
0 голосов
/ 15 февраля 2019
#include <iostream>
using namespace std;

int main()
{
    std::ostream o(nullptr);
    true ? std::ostream(nullptr) : std::ostream(nullptr); // A
    true ? std::ostream(nullptr) : o; //B
    return 0;
}

Мне было интересно, почему A компилируется нормально, в то время как B завершается с ошибкой:

prog.cpp: In function ‘int main()’:
prog.cpp:7:33: error: ‘std::basic_ostream<_CharT, _Traits>::basic_ostream(const std::basic_ostream<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’ is protected within this context
  true ? o : std::ostream(nullptr);
                                 ^

Итак, я нашел этот сайт: https://docs.microsoft.com/en-us/cpp/cpp/conditional-operator-q?view=vs-2017, который говорит, что когда троичный операторимеет аргументы (под «аргументами» я подразумеваю те, что слева и справа от :), тогда это может привести к копированию аргументов, приведению и т. д. ... Это имеет смысл, поскольку std::ostream имеет конструкторы копирования, определенные как protected,Таким образом, в A троичный оператор получает оба аргумента одного типа, копирование не выполняется, поэтому проблем нет.В то время как в B троичный оператор получает аргументы разных типов, что, очевидно, вызывает необходимость в копировании, которое не разрешено для std::ostream.Пока все вроде ОК.

Но потом я попробовал вот что:

#include <iostream>
using namespace std;

int main()
{
    std::ostream o(nullptr);
    std::ostream & oRef = o;
    std::ostream && oRRef = std::ostream(nullptr);
    true ? std::ostream(nullptr) : std::ostream(nullptr); // A
    true ? std::ostream(nullptr) : o; //B
    true ? std::ostream(nullptr) : oRef; // C
    true ? std::ostream(nullptr) : oRRef; // D
    true ? std::ostream(nullptr) : std::move(oRRef); // E
    return 0;
}

C, D и E также не работают с похожими ошибками.

Итак, у меня есть несколько вопросов.Какой тип выражения: std::ostream(nullptr)?Правда ли, что троичный оператор будет делать копии своих аргументов, когда они имеют разные типы, но никогда не будет делать копии, когда они одного типа?Что-то еще, что я пропустил или должен знать?

1 Ответ

0 голосов
/ 15 февраля 2019

Стандарт содержит некоторые сложные правила, касающиеся того, как оценивается условное выражение ([expr.cond]).Но вместо того, чтобы привести эти правила в кавычки, я собираюсь объяснить, как вы должны думать о них.

Результатом условного выражения может быть lvalue, xvalue или prvalue.Но какой из них это должен быть известен во время компиляции.(Категория значений выражения никогда не может зависеть от того, что происходит во время выполнения).Легко видеть, что если и второе, и третье выражения являются lvalue одного и того же типа, то результат также может быть сделан lvalue, и копирование не требуется.Если и второе, и третье выражения являются значениями одного и того же типа, то, начиная с C ++ 17, копирование также не должно происходить - значение типа T представляет отложенную инициализацию объекта типа * 1004.* и компилятор просто выбирает, основываясь на условии, какое из этих двух значений будет передано в конечном итоге для инициализации объекта.

Но когда одно выражение является значением l, а другое - значениемтого же типа, то результат должен быть prvalue.Если в стандарте сказано, что результатом будет lvalue, это будет нелогично, так как условие может привести к выбору операнда prvalue, и вы не сможете преобразовать prvalue в lvalue.Но вы можете сделать это наоборот.Таким образом, стандарт говорит, что когда один операнд является lvalue, а другой - prvalue того же типа, тогда lvalue должен подвергаться преобразованию lvalue в rvalue.И если вы попытаетесь преобразовать значение lvalue в rvalue для объекта std::ostream, программа будет плохо сформирована, так как конструктор копирования удален.

Таким образом:

  • InA, оба операнда являются значениями типа prvalue, поэтому нет преобразования lvalue в rvalue;это нормально в C ++ 17 (но не в C ++ 14).
  • В B преобразование lvalue-to-rvalue необходимо для o, поэтому компиляция не производится.
  • В C oRef является lvalue, поэтому преобразование lvalue в rvalue по-прежнему требуется, поэтому оно также не будет компилироваться.
  • В D, oRRef по-прежнему является lvalue (поскольку имя ссылки на rvalue является lvalue).
  • В E один аргумент является prvalue, а другой - xvalue.Xvalue по-прежнему необходимо преобразовать в prvalue, чтобы сделать результат prvalue.

Случай E заслуживает некоторых дополнительных замечаний.В C ++ 11 было ясно, что если один аргумент является значением xvalue, а другой является prvalue одного и того же типа, xvalue должен пройти (ошибочно названное) преобразование lvalue-в-значение, чтобы получить значение prvalue.В случае std::ostream используется конструктор защищенного перемещения (поэтому программа нарушает контроль доступа членов).В C ++ 17 можно было бы подумать об изменении правила, чтобы вместо преобразования значения xvalue в значение prvalue значение prvalue материализовалось для получения значения xvalue, устраняя необходимость в перемещении.Но это изменение не имеет очевидной выгоды, и сомнительно, является ли это наиболее разумным поведением, поэтому, вероятно, поэтому оно и не было сделано (если оно даже рассматривалось).

...