Create an optional tuple

3.9k views Asked by At

I don't understand why there is no optional tuple, and I mean by that, something like this ; optional<int,string,char> that would combine an optional int, optional string and optional char.

You can think of it like an optimized std::tuple<boost::optional<T>...>

where the booleans used by the optionals would be all together at the end of the structure in order to pack it, or even better, be stored in a bitset.

That may reduce the memory of the structure A LOT, and also is more elegant :

std::tuple<boost::optional<int>,boost::optional<string>,boost::optional<char>>

VS

optional<int,string,char>

I can think of a way of doing this, using the implementaion of boost::optional and variadic templates, but before starting this, I would like to know if this is a good idea, what would be a better way of implementing this, what are the difficulties I would be facing ?

EDIT :

Basically why I don't like std::tuple<boost::optional<T>...>;

Since an optional<T> is a union of T and a bool :

enter image description here

The new structure can save a lot of memory !!

3

There are 3 answers

5
Jarod42 On BEST ANSWER

You may implement yourself, something similar to:

/**
 *  Similar to `optional`, but take the bool as argument.
 */
template <typename T>
class out_optional
{
public:
    out_optional() {}

    out_optional(const out_optional&) = delete;
    out_optional& operator =(const out_optional&) = delete;

    void destruct(bool b) { if (b) { reset(b); } }

    void reset(bool& b) { if (b) { reinterpret_cast<T*>(data)->~T(); b = false; } }
    void reset(bool& b, const T& value) { reset(b); new (data) T{value}; b = true; }
    void reset(bool& b, T&& value) { reset(b); new (data) T{value}; b = true; }

    const T* get_ptr(bool b) const { return b ? reinterpret_cast<T*>(data) : nullptr; }
    T* get_ptr(bool b) { return b ? reinterpret_cast<T*>(data) : nullptr; }

    const T& get(bool b) const { assert(b); return *get_ptr(b); }
    T& get(bool b) { assert(b); return *get_ptr(b); }

    // Other stuff as swap, pseudo assignment/move, more constructors

private:
    alignas(T) char data[sizeof(T)];
};

/**
 * 'Tuple' of optional, packaged with bool at the end.
 */
template <typename ... Ts>
struct multi_optional
{
    template <std::size_t I>
    using type = typename std::tuple_element<I, std::tuple<Ts...>>::type;

    static_assert(std::is_same<int, type<0>>::value, "");
public:
    multi_optional() = default;

    ~multi_optional()
    {
        destruct(std::index_sequence_for<Ts...>());
    }

    multi_optional(const multi_optional&) = delete; // To be implemented.
    multi_optional& operator =(const multi_optional&) = delete; // To be implemented.

    template <std::size_t I>
    const auto* get_ptr() const { return std::get<I>(data).get_ptr(flags[I]); }

    template <std::size_t I>
    auto* get_ptr() { return std::get<I>(data).get_ptr(flags[I]); }

    template <std::size_t I>
    const auto& get() const { return std::get<I>(data).get(flags[I]); }

    template <std::size_t I>
    auto& get() { return std::get<I>(data).get(flags[I]); }

    template <std::size_t I>
    void reset() { return std::get<I>(data).reset(flags[I]); }

    template <std::size_t I>
    void reset(const type<I>& value) { return std::get<I>(data).reset(flags[I], value); }


   // Other stuff as copy/move assignment/constructor, ...

private:
    template <std::size_t ... Is>
    void destruct(std::index_sequence<Is...>)
    {
        int dummy[] = { 0, (std::get<Is>(data).destruct(flags[Is]), 0)... };
        static_cast<void>(dummy);
    }

private:
    std::tuple<out_optional<Ts>...> data;
    std::array<bool, sizeof...(Ts)> flags = {{}};
};

Live Demo

3
sehe On

Is it not a good option to use a template alias?

template <typename... T> using optuple = 
    boost::optional<std::tuple<T...> >;

Or, in fact the other variation if you preferred;

template <typename... T> using optuple = 
    std::tuple<boost::optional<T>...>;

Now you can just write

optuple<std::string, double, int> x;

and get the meaning you chose.

Of course, if only one of the tuple members is ever filled, this already exists: boost::variant<T...>

1
Puppy On

The structure you want can already be created as a specialization of tuple<T>. There's no purpose in introducing an additional structure. There's nothing different here compared to making a special vector just to employ EBO.

What you want is covered perfectly well by the existing interfaces. If you desire another implementation, you could always specialize I guess or roll your own.