Подтвердить что ты не робот

Можно ли создать константную ссылку на результат тернарного оператора в С++?

В этом коде есть что-то совершенно неочевидное:

float a = 1.;

const float & x = true ? a : 2.; // Note: `2.` is a double

a = 4.;

std::cout << a << ", " << x;

вывод clang и gcc:

4, 1

Можно было бы наивно ожидать, что одно и то же значение будет напечатано дважды, но это не так. Проблема здесь не имеет ничего общего со ссылкой. Существуют некоторые интересные правила, определяющие тип ? :. Если два аргумента имеют различный тип и могут быть отброшены, они будут использовать временный. Ссылка будет указывать на временный ? :.

Приведенный выше пример компилируется отлично, и он может или не может выдавать предупреждение при компиляции с -Wall в зависимости от версии вашего компилятора.

Вот пример того, как легко получить это неправильно в законно выглядящем коде:

template<class Iterator, class T>
const T & min(const Iterator & iter, const T & b)
{
    return *iter < b ? *iter : b;
}

int main()
{
    // Try to remove the const or convert to vector of floats
    const std::vector<double> a(1, 3.0);

    const double & result = min(a.begin(), 4.);

    cout << &a[0] << ", " << &result;
}

Если ваша логика после этого кода предполагает, что любые изменения на a[0] будут отражены на result, это будет неправильно в случаях, когда ?: создает временную. Кроме того, если в какой-то момент вы делаете указатель на result, и вы используете его после того, как result выходит за пределы области видимости, произойдет ошибка сегментации, несмотря на то, что ваш оригинальный a не вышел из области видимости.

Я чувствую, что есть серьезные причины НЕ использовать эту форму помимо "проблем с ремонтопригодностью и чтением", упомянутых здесь здесь, особенно при написании шаблонного кода, где некоторые из ваших типов и их const'ness может оказаться вне вашего контроля.

Итак, мой вопрос: безопасно ли использовать const & для тернарных операторов?

P.S. Бонусный пример 1, дополнительные осложнения (см. Также здесь):

float a = 0;
const float b = 0;
const float & x = true ? a : b;

a = 4;
cout << a << ", " << x;

вывод clang:

4, 4

gcc 4.9.3 вывод:

4, 0

С clang этот пример компилируется и работает как ожидалось, но с последними версиями gcc (

P.S.2 Бонусный пример 2, отличный для интервью;):

double a = 3;

const double & a_ref = a;

const double & x = true ? a_ref : 2.;

a = 4.;

std::cout << a << ", " << x;

выход:

4, 3
4b9b3361

Ответ 1

Прежде всего, результатом условного оператора является либо значение glvalue, обозначающее выбранный операнд, либо значение prvalue, значение которого исходит из выбранного операнда.

Исключение, указанное в T.C.: если хотя бы один операнд имеет тип класса и имеет оператор преобразования в ссылку, результатом может быть lvalue, обозначающий объект, обозначенный возвращаемым значением этого оператора; и если назначенный объект на самом деле является временным, может произойти зависание ссылки. Это проблема с такими операторами, которые предлагают неявное преобразование prvalues ​​в lvalues, а не проблему, введенную самим условным оператором.

В обоих случаях безопасно привязывать ссылку к результату, применяются обычные правила привязки ссылки к lvalue или prvalue. Если ссылка связывается с prvalue (либо результатом prvalue условного, либо значением prvalue, инициализированным из результата lvalue условного выражения), время жизни prvalue увеличивается, чтобы соответствовать времени жизни ссылки.


В исходном случае условное обозначение:

true ? a : 2.

Второй и третий операнды: "lvalue типа float" и "prvalue типа double" . Это случай 5 в cppreference summary, результатом которого является "prvalue типа double" .

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

В заключение, после const float & x = true ? a : 2.;, x является значением l, обозначающим a float, значение которого является результатом преобразования a в double и обратно. (Не уверен, что на моей голове будет сравниваться, равное a). x не привязан к a.


В бонусном случае 1 второй и третий операнды условного оператора "lvalue типа float" и "lvalue типа const float". Это случай 3 той же ссылки cppreference,

оба являются значениями одной и той же категории значений и имеют один и тот же тип, за исключением cv-qualification

Поведение состоит в том, что второй операнд преобразуется в "lvalue типа const float" (обозначая один и тот же объект), а результатом условного выражения является "lvalue типа const float", обозначающий выделенный объект.

Затем вы привязываете const float & к "lvalue типа const float", который связывается напрямую.

Итак, после const float & x = true ? a : b;, x напрямую привязан либо к a, либо к b.


В бонусе 2, true ? a_ref : 2.. Второй и третий операнды являются "lvalue типа const double" и "prvalue типа double" , поэтому результат "prvalue типа double" .

Затем вы привязываете это к const double & x, который является прямым связыванием, поскольку const double ссылается на double.

Итак, после const double & x = true ? a_ref : 2.;, тогда x является значением l, обозначающим double с тем же значением, что и a_ref (но x не привязано к a).

Ответ 2

Короче: да, это может быть безопасно. Но вам нужно знать, чего ожидать.

Ссылки Lvalue const и ссылки rvalue могут использоваться для продления времени жизни временных переменных (минус исключения, указанные ниже).

Кстати, мы уже узнали из вашего предыдущего вопроса, что gcc 4.9 - это не лучшая ссылка для такого теста. Бонусный пример 1, скомпилированный с gcc 6.1 или 5.3, дает точно такой же результат, как и с clang. Как и предполагалось.

Цитаты из N4140 (выделенные фрагменты):

[class.temporary]

Существует два контекста, в которых временные другая точка, чем конец полного выражения. [...]

Второй контекст - это когда привязка привязана к временному. временный, к которому привязана ссылка, или временное, которое является полный объект подобъекта, к которому привязана ссылка сохраняется на протяжении всей жизни ссылки, за исключением: [нет соответствующих положения к этому вопросу]

[expr.cond]

3) В противном случае, если второй и третий операнды имеют разные типы и либо имеет (возможно, cv-qualified) тип класса, либо если оба являются значениями glvalues той же категории значений и того же типа, за исключением cv-qualification, делается попытка преобразовать каждый из этих операндов к типу другого.

  • Если E2 является lvalue: E1 может быть преобразовано в соответствие с E2, если E1 может быть неявно преобразовано (раздел 4) в тип "ссылка на lvalue на T2", при условии ограничения, что при преобразовании ссылка должна привязываться непосредственно к lvalue

  • [...]

  • Если E2 является значением prvalue или если ни одно из приведенных выше преобразований не может быть выполнено, и хотя бы один из операндов имеет (возможно, cv-квалификацию) тип класса:

    • В противном случае (т.е. если E1 или E2 имеет тип некласса, или если оба они имеют типы классов, но базовые классы не являются либо тот же или один базовый класс другого): E1 можно преобразовать в соответствие E2, если E1 можно неявно преобразовать в тип, выражение E2 имел бы, если E2 был преобразован в prvalue (или тип it имеет, если E2 является prvalue)

[...] Если ни один из них не может быть преобразован, операнды остаются неизменными и дальнейшая проверка выполняется, как описано ниже. Если ровно один возможно преобразование, которое применяется к выбранному операнд и преобразованный операнд используются вместо оригинала операнд для остальной части этого раздела.

4) Если второй и третий операнды являются значениями одного и того же значения категории и имеют один и тот же тип, результат такого типа и значения категория [...]

5) В противном случае результатом будет prvalue. Если второй и третий операнды не имеют одного и того же типа и имеют (возможно cv-qualit) тип класса [...]. В противном случае преобразования определены, а преобразованные операнды используются на месте из исходных операндов для остальной части этого раздела.

6) Lvalue-to-rvalue, от массива до указателя и от функции к указателю стандартные преобразования выполняются во втором и третьем операндах. После этих преобразований будет выполнено одно из следующих действий:

  • Второй и третий операнды имеют тип арифметики или перечисления; обычные арифметические преобразования выполняются, чтобы привести их к общий тип, и результат этого типа.

Итак, первый пример хорошо определен, чтобы сделать то, что вы испытали:

float a = 1.;
const float & x = true ? a : 2.; // Note: `2.` is a double
a = 4.;
std::cout << a << ", " << x;

x является ссылкой, привязанной к временному объекту типа float. Он не ссылается на a, потому что выражение true ? float : double определено, чтобы дать double - и только тогда вы преобразовываете этот double в новый и другой float при назначении его x > .


В вашем втором примере (бонус 1):

float a = 0;
const float b = 0;
const float & x = true ? a : b;

a = 4;
cout << a << ", " << x;

тернарный оператор не должен делать никаких преобразований между a и b (кроме соответствия cv-квалификаторов), и он дает lvalue, ссылаясь на const float. x псевдонимы a и должны отражать изменения, сделанные в a.


В третьем примере (бонус 2):

double a = 3;
const double & a_ref = a;
const double & x = true ? a_ref : 2.;

a = 4.;
std::cout << a << ", " << x;

В этом случае E1 можно преобразовать в соответствие с E2, если E1 может быть неявно преобразовано в тип, который имеет [...] [E2], если E2 - значение pr20. Теперь, что prvalue имеет то же значение, что и a, но является другим объектом. x не псевдоним a.

Ответ 3

Можно ли создать константную ссылку на результат тройного оператора в С++?

Как Аскер, я бы подвел итоги дискуссии; Это нормально для не-шаблонного кода, на довольно современных компиляторах, с Warnings on. Для шаблонного кода, в качестве обозревателя кода, я бы вообще не поощрял его.