What object is produced by ternary operator in C++?

Viewed 162

The following program

#include <optional>
#include <iostream>

int main()
{
    std::optional<int> a;
    constexpr bool x = true;

    const std::optional<int> & b = x ? a : std::nullopt;
    std::cout << (&a == &b);

    const std::optional<int> & c = x ? a : (const std::optional<int> &)std::nullopt;
    std::cout << (&a == &c);

    const std::optional<int> & d = x ? a : (const std::optional<int>)std::nullopt;
    std::cout << (&a == &d);
}

prints 010 even in case of compiler optimization: https://gcc.godbolt.org/z/asTrzdE3c

Could you please explain why it is so, and what is the difference between seemingly identical 3 cases?

2 Answers

In a laymen words, ternary operator will return an object of a type to which both operands are implicitly convertible (Tidbit: this is why it is used behind the scenes in the metafunction std::common_type).

If both operands are of the common type, you will get a reference to the one selected. If not, a temporary object of the required type will be created. This is the case in your first and third example.

First you have to undesund what is the type of ternary operator result.

x ? a : std::nullopt;

Here a is variable which can be reference and std::nullopt is something which is implicitly converted to optional of matching type (here std::optional<int>). So conversion of std::nullopt ends with creation of temporary value. To match type a is also copied.

So ternary operator deduces type to be a value of type std::optional<int> which becomes a temporary object. New instance of std::optional<int> is created.

Now const auto & is able to prolong lifetime of temporaries. So b is reference to std::optional<int> which is a temporary of prolonged lifetime.

d is same scenario, but it is more explicit.

c has (const std::optional<int> &)std::nullopt which is creates temporary object of std::optional<int> with prolonged lifetiem. Here ternary operator has as argument something what is std::optional<int>& and const std::optional<int>& so it is able to pass first arguments reference return type.

See what cppinsights generates with your code:

#include <optional>
#include <iostream>

int main()
{
  std::optional<int> a = std::optional<int>();
  constexpr const bool x = true;
  const std::optional<int> & b = x ? std::optional<int>(a) : std::optional<int>(std::nullopt_t(std::nullopt));
  std::cout.operator<<((&a == &b));
  const std::optional<int> & c = x ? a : static_cast<const std::optional<int>>(std::optional<int>(std::nullopt_t(std::nullopt)));
  std::cout.operator<<((&a == &c));
  const std::optional<int> & d = (x ? std::optional<int>(a) : static_cast<const std::optional<int>>(std::optional<int>(std::nullopt_t(std::nullopt))));
  std::cout.operator<<((&a == &d));
}
Related