Omnifarious
Omnifarious

Reputation: 56048

How do I make a simplified version of boost::variant?

I have a much simpler set of requirements and do not need much of variant's machinery. I also do not want to depend on boost if I can help it.

I need to store either an arbitrary type known at compile time (that may be void). It is either move constructable or copy constructable, and if either of those throw, it is permissible for the contained value to be undefined.

Instead of this value, it may also either contain an ::std::exception_ptr or an ::std::error_code.

::boost::variant<T, ::std::exception_ptr, ::std::error_code> would work if T is allowed to be void. Except that ::boost::variant provides the 'never-empty guarantee' which I don't actually need in this instance. And, if I understand how it works correctly, it's not very compatible with types that can be moved but not copied.

Right now I'm writing a lot of duplicated code that I shouldn't have to be writing to handle each of these types separately. I'm also storing a copy of an object of each type and flag values saying which is relevant. Lastly, void gives the whole system conniptions and is requiring I write specializations all over the place.

Is there a better way?

Here's a simplified example of what I have. This is basically a class designed to hold a result for transmission to another thread, sort of like a future:

template <typename ResultType>
class stored_result {
 public:
   stored_result() : is_val_(false), is_err_(false), is_exception_(false) { }

   void set_bad_result(::std::error err) {
      is_err_ = true;
      error_ = ::std::move(err);
   }
   void set_bad_result(::std::exception_ptr exception) {
      is_exception_ = true;
      exception_ = ::std::move(exception);
   }
   void set_result(ResultType res) {
      is_val_ = true;
      val_ = ::std::move(res);
   }

   ResultType result() {
      if (is_val_) {
         is_val_ = false;
         return ::std::move(val_);
      } else if (is_exception_) {
         is_exception_ = false;
         ::std::rethrow_exception(::std::move(exception_));
      } else if (is_error_) {
         is_error_ = false;
         throw ::std::system_error(error_);
      } else {
         throw ::std::runtime_error("Asked for result when there was none.");
      }
   }

 private:
   bool is_val_, is_err_, is_exception_;
   T val_;
   ::std::exception_ptr exception_;
   ::std::error_code error_;
};

Upvotes: 4

Views: 368

Answers (2)

Luc Danton
Luc Danton

Reputation: 35449

An example of how to handle void in a transparent manner with the code you have right now:

struct empty_type {};

template<typename T>
using Store = typename std::conditional<std::is_void<T>::value, empty_type, T>::type;

template<typename T>
T&&
restore(T&& t)
{
    return std::forward<T>(t);
}

void
restore(empty_type)
{}

template <typename ResultType>
class stored_result {
public:
    // snipped everything that is left unchanged

    template<
        typename U = ResultType
        , typename std::enable_if<
            !std::is_void<U>::value
            , int
        >::type = 0
    >          
    void set_result(U res) {
        is_val_ = true;
        val_ = std::move(res);
    }

    template<
        typename U = ResultType
        , typename std::enable_if<
            std::is_void<U>::value
            , int
        >::type = 0
    >          
    void set_result() {
        is_val_ = true;
    }

    ResultType result() {
        if (is_val_) {
            is_val_ = false;
            return restore(std::move(val_));
        } else if {
            // rest as before
    }

private:
    Store<T> val_;
};

Although the code is untested may have some kinks.

Upvotes: 2

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145279

[Edit: the question was edited, adding example code, after I wrote this].

It appears that what you're after is a way of returning either a valid result (of arbitrary type) from a function, or something that indicates a failure.

If so then the code below goes a good way towards solving your problem, namely the part of returning a result of arbitrary type (this class is similar to boost::optional, which in turn is based on Barton and Nackman's Fallible class).

For the error indication, simply replace the boolean with the error info, and replace the conceptual "none" with the conceptual "failure":

#pragma once
// #include <cpp/optional.h>
// Author: Alf P. Steinbach. License: Boost 1.0


//----------------------------------------------- Dependencies:

#include <cpp/type_properties.h>        // cpp::IsPod_
#include <cpp/throwing.h>               // cpp::hopefully, cpp::throwX
#include <vector>                       // std::vector


//----------------------------------------------- Interface:

namespace cpp {

    namespace detail {
        using std::vector;

        template< class Type, Podness::Enum podNess = Podness::isPod >
        class ValueWrapper_
        {
        private:
            Type    v_;
        public:
            Type const& ref() const { return v_; }

            ValueWrapper_() {}       // No initialization
            ValueWrapper_( Type const v ): v_( v ) {}
        };

        template< class Type >
        struct ValueWrapper_< Type, Podness::isNotPod >
        {
        private:
            vector<Type>    v_;     // General but incurs allocation overhead.
        public:
            Type const& ref() const { return v_[0]; }

            ValueWrapper_() {}       // Supports apparent no initialization.
            ValueWrapper_( Type const v ): v_( 1, v ) {}
        };
    }    // namespace detail

    template< class Type >
    class Optional_
    {
    private:
        typedef detail::ValueWrapper_<Type, Podness_<Type>::value > Wrapper;

        Wrapper const   value_;
        bool const      isNone_;

        Optional_& operator=( Optional_ const& );         // No such.

    public:
        bool isNone() const { return isNone_; }

        Type const& value() const
        {
            hopefully( !isNone_ )
                || throwX( "Optional_::value(): there is no value" );
            return value_.ref();
        }

        Optional_(): isNone_( true ) {}
        Optional_( Type const& v ): value_( v ), isNone_( false ) {}

        static Optional_ none() { return Optional_(); }
    };

    template<>
    class Optional_< void >
    {
    private:
        Optional_& operator=( Optional_ const& );         // No such.

    public:
        bool isNone() const { return true; }

        void value() const
        {
            throwX( "Optional_::value(): there is no value" );
        }

        Optional_() {}
        static Optional_ none() { return Optional_(); }
    };
}  // namespace cpp

Upvotes: 2

Related Questions