Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::experimental::optional inside constexpr function

I would like to use the optional idiom inside my constexpr function to easily clarify if the variable is set or not.

What I have tried with std::experimental::optional:

constexpr bool call()
{
  std::experimental::optional<bool> r; 
  r = true; // Error
  // Similar error with:
  // r = std::experimental::optional<bool>(true);

  if (!r)
  {
    return false;
  }
  return *r;
}

I get the error: call to non-constexpr function - so the assignment is not possible, because this operation cannot be constexpr (Example).

But if I implement my own (very ugly, just for example) optional class, it works, because I don´t implement the assignment operator/constructor explicit.

template<typename T>
struct optional
{
  bool m_Set;
  T m_Data;

  constexpr optional() :
    m_Set(false), m_Data{}
  {
  }

  constexpr optional(T p_Data) :
    m_Set(true), m_Data(p_Data)
  {
  }

  explicit constexpr operator bool()
  {
    return m_Set;
  }

  constexpr T operator *()
  {
    return m_Data;
  }
};

How could I use std::..::optional in the same context with assignment inside constexpr functions?

like image 423
Viatorus Avatar asked Mar 12 '23 02:03

Viatorus


2 Answers

Basically, you can't. The problem with your simple implementation is that it requires T be default-constructible - if this is not the case, this won't work.

To get around this, most implementation use either a union or some (suitably aligned) storage that can hold a T. If you are passed a T in the constructor, then all well and good, you can initialize this directly (hence it will be constexpr). However, the tradeoff here is that when calling operator=, copying the value across may require a placement-new call, which cannot be constexpr.

For example, from LLVM:

template <class _Up,
      class = typename enable_if
              <
                  is_same<typename remove_reference<_Up>::type, value_type>::value &&
                  is_constructible<value_type, _Up>::value &&
                  is_assignable<value_type&, _Up>::value
              >::type
          >
_LIBCPP_INLINE_VISIBILITY
optional&
operator=(_Up&& __v)
{
    if (this->__engaged_)
        this->__val_ = _VSTD::forward<_Up>(__v);
    else
    {
        // Problem line is below - not engaged -> need to call 
        // placement new with the value passed in.
        ::new(_VSTD::addressof(this->__val_)) value_type(_VSTD::forward<_Up>(__v));
        this->__engaged_ = true;
    }
    return *this;
}

As for why placement new is not constexpr, see here.

like image 58
Yuushi Avatar answered Mar 15 '23 09:03

Yuushi


This is not possible, as explained in n3527:

Making optional a literal type

We propose that optional<T> be a literal type for trivially destructible T's.

constexpr optional<int> oi{5};
static_assert(oi, "");            // ok
static_assert(oi != nullopt, ""); // ok
static_assert(oi == oi, "");      // ok
int array[*oi];                   // ok: array of size 5 

Making optional<T> a literal-type in general is impossible: the destructor cannot be trivial because it has to execute an operation that can be conceptually described as:

~optional() {
  if (is_engaged()) destroy_contained_value();
}

It is still possible to make the destructor trivial for T's which provide a trivial destructor themselves, and we know an efficient implementation of such optional<T> with compile-time interface — except for copy constructor and move constructor — is possible. Therefore we propose that for trivially destructible T's all optional<T>'s constructors, except for move and copy constructors, as well as observer functions are constexpr. The sketch of reference implementation is provided in this proposal.

In other words, it's not possible to assign a value to r even if you mark it as constexpr. You must initialize it in the same line.

like image 24
uh oh somebody needs a pupper Avatar answered Mar 15 '23 10:03

uh oh somebody needs a pupper