2

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?

Fedor
  • 17,146
  • 13
  • 40
  • 131
  • 2
    See __Conditional operator__ https://en.cppreference.com/w/cpp/language/operator_other as it's about 2 pages of "_if-this-then-that"_ – Richard Critten Jul 08 '21 at 15:20
  • 1
    As another datapoint, if you compare the optionals directly (rather than taking their address) it prints `111` – mattlangford Jul 08 '21 at 15:20
  • 3
    In a laymen words, ternary operator will return an object of a type to which both operands are implicitly convertible (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. – SergeyA Jul 08 '21 at 15:27
  • 1
    @SergeyA Sounds like an answer. – Ted Lyngmo Jul 08 '21 at 15:27
  • 2
    @TedLyngmo I felt it is a bit short on normative to be an answer, but I can post it if people think it is useful. – SergeyA Jul 08 '21 at 15:54

2 Answers2

3

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.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
2

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));
}
Marek R
  • 32,568
  • 6
  • 55
  • 140