0

A sum type such as std::optional is implemented with conditionally trivial member functions because the template parameter may have a non trivial destructor. It's destructor is thus implemented as follows:

~optional()
{
    if (bool(*this))
    {         
      payload.~T();
    }
}

~optional() requires std::is_trivially_destructible_v<T> = default;

And to express a disengaged state we write:

constexpr optional& operator = (nullopt_t) noexcept
{
     if (bool(*this))
     {
          payload.~stored_type();
          engaged = false;
      }
      return *this;
} 

But what if our sum type's template parameter is restricted to being trivially destructible...

template <std::integral T>
class trivial_sum_type;

How do we ensure, for instance within a swap member function, that when we exchange values from an engaged to a disengaged object that the destructor for the value within the previously engaged object is called? And vice versa

template <std::integral T>
class trivial_sum_type
{
   //everything else same as std::optional except it is restriced to a trivial type i.e std::integral 

   constexpr void swap(const trivial_sum_type& rhs)
   {
         if (bool(*this) and bool(rhs))
         {
             std::swap(payload, *rhs);
         }
         else if (bool(*this) and not bool(rhs))
         {
             new(std::addressof(payload))stored_type(std::move(payload));
             payload.~stored_type(); //what if stored_type is an int 
         }
         else if (not bool(*this) and bool(rhs))
         {
             new(std::addressof(payload))stored_type(std::move(*rhs));
             rhs.payload.~stored_type(); //same issue here
         }
   } 
   
   ~trivial_sum_type() = default;

   private:

        struct empty_byte{};
        
        union
        { 
           T payload;
           empty_byte empty;
        } 

        bool engaged;
}

My confusion is here: you do not explicitly write a destructor for a trivially destructible type within the sum type's destructor function, but does it also mean that when you swap or express a disengaged state you should not call a destructor on such types. Which leads me to the question do trivial types have destructors? And how do you destroy the value of a trivially destructible type within a disengaged trivially destructible sum type? Hopefully...am not stack raving mad and my lack of understanding makes sense

1 Answers1

1

I interpret the question as asking for payload.~stored_type(); when the stored_type is int or some other fundamental type that does not have a destructor. A type that is trivially destructible is not exactly that, it may have a destructor (that is not user provided, not virtual and all base classes and non-static members have trivial destructors, see https://en.cppreference.com/w/cpp/language/destructor#Trivial_destructor).

In simple words, a trivial destructor is a destructor that does nothing. It can be called but its not an issue when it is not called. Your question seems to be about types that do not have a destructor.

For such cases (when there is no destructor to be called) there are pseudo destructors (https://en.cppreference.com/w/cpp/language/destructor):

In generic contexts, the destructor call syntax can be used with an object of non-class type; this is known as pseudo-destructor call: see member access operator.

For example, consider the following, which calls the int pseudo-destructor (and then leaks the memory):

template <typename T>
void foo() {
    T* x = new T;
    (*x).~T();       // no issue, int has a pseudo destructor
}


int main()
{
    foo<int>();
}
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185