C++ My implementation of a simple sum type (variant)
Construction and destruction of non-trivial types

Unfortunately, your class does not really support non-trivial types, because your class copies the representation of the supplied values and instead of invoking the proper copy constructor.  In other words, you create no object in the Either class, so the behavior of reinterpret_casting the storage and calling the destructor is undefined.

Interestingly, you store the destructor function object but never actually call it, so the class ends up working for trivial types, which do not require invocations to the corresponding constructor or destructor.  However, making your code work by letting two errors cancel out each other is not really a good idea.

The copy and move semantics of your class is also broken.  We'll try to fix this problem later.

Use of runtime type information (typeid)

The usage of typeid is a bit overkill here and not optimal, because it forces the user to pass exactly the same type as one of the stored types, disallowing conversions.  In fact, simply storing an index seems to suffice.

std::variant uses overload resolution to deduce the type, which is hard to implement.  You may consider supporting in_place construction.  (On the other hand, std::variant is typically implemented with unions because of constexpr requirements.)

The storage


using Bigest = std::conditional_t&lt;sizeof(T1) &lt;= sizeof(T2), T1, T2&gt;;
alignas(sizeof(Bigest)) std::byte storage[sizeof(Bigest)];
std::optional&lt;std::type_index&gt; conatinedType;
std::function&lt;void(std::byte*)&gt; destructor;



Consider using std::aligned_storage.  As I said above, we can simply store an index.

using storage_type = std::aligned_storage_t&lt;std::max( sizeof(T1),  sizeof(T2)),
                                            std::max(alignof(T1), alignof(T2))&gt;;
storage_type storage;
std::size_t index;


For valueless variants, we can provide a special index:

static constexpr std::size_t npos = std::numeric_limits&lt;std::size_t&gt;::max();


The constructors

The default constructor of std::variant default-constructs the first alternative type.  You can go into the valueless state instead:

Either() noexcept
    : index{npos}
{
}


Here's how you'd support in place constructors:

template &lt;std::size_t I, typename... Args&gt;
explicit Either(std::in_place_t&lt;I&gt;, Args&amp;&amp;... args)
{
    construct&lt;I&gt;(std::forward&lt;Args&gt;(args)...);
}

template &lt;std::size_t I, typename U, typename... Args&gt;
explicit Either(std::in_place_t&lt;I&gt;, std::initializer_list&lt;U&gt; ilist, Args&amp;&amp;... args)
{
    construct&lt;I&gt;(ilist, std::forward&lt;Args&gt;(args)...);
}


where the private member function template construct is defined like

template &lt;std::size_t I&gt;
struct alternative_type {
    static_assert(I &lt; 2);
    using type = std::conditional_t&lt;I == 0, T1, T2&gt;;
};
template &lt;std::size_t I&gt;
using alternative_type_t = typename alternative_type&lt;I&gt;::type;

// not exception safe
template &lt;std::size_t I, typename... Args&gt;
void construct(Args&amp;&amp;... args)
{
    index = I;
    ::new (static_cast&lt;void*&gt;(&amp;storage))
        alternative_type_t&lt;I&gt;(std::forward&lt;Args&gt;(args));
}


Special member functions

As I said before, you need to implement the copy/move/destruction operations.  Here's the copy constructor for example: (SFINAE and explicit issues are omitted for simplicity.)

Either(const Either&amp; other)
{
    if (other.index == 0) {
        construct&lt;T1&gt;(other.get&lt;0&gt;());
    } else if (other.index == 1) {
        construct&lt;T2&gt;(other.get&lt;1&gt;());
    } else {
        index = npos;
    }
}