4

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_;
};
Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • You can map `void` to a unique empty type in a transparent manner to the user. This would allow you to reuse `boost::variant`, but won't necessary help with specializations. The biggest show-stopper for `boost::variant` is move construction however. – Luc Danton Apr 03 '12 at 03:10
  • It sounds a lot like premature optimization. – Cheers and hth. - Alf Apr 03 '12 at 03:12
  • @LucDanton: _nod_ Yeah, I saw that in a different answer. I think I can wrap my head around how that works. – Omnifarious Apr 03 '12 at 03:13
  • 1
    @Cheersandhth.-Alf: No, it's a design-level question. I keep on writing all this duplicated code all over the place. That's not OK for a lot of different reasons. – Omnifarious Apr 03 '12 at 03:14
  • 1
    I'm note sure how to go about answering you but in case you're not familiar with it already you may do well to check out `std::aligned_storage`. – Luc Danton Apr 03 '12 at 03:25
  • @Luc: it appears that the amount of `std::aligned_storage` in Visual C++ 10.0, is rather insignificant... :-( – Cheers and hth. - Alf Apr 03 '12 at 03:39
  • @Cheersandhth.-Alf how is designing code properly in any way premature optimization??? –  Apr 03 '12 at 06:27
  • @lttlrck: I think he was referring to my desire to use variant to avoid the extra space overhead of storing one of several different values. And in that I think he's right. But that wasn't my primary concern. – Omnifarious Apr 03 '12 at 13:56

2 Answers2

2

[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
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Yeah, this helps me think through the problem. I see now how I can make this work. The overlaying all the different values on top of each other isn't actually that important to me. As you pointed out, premature optimization. This solves the rest of the problem neatly. – Omnifarious Apr 03 '12 at 03:42
  • This is a really interesting way to handle a value that may not actually be there. Right now I rely on the value being default constructible. But it would be nice, in the future to work for values that were only moveable or only copy constructible. Unfortunately, my biggest hangup right now is handling void sanely, and Luc has a better solution than the one I have that ends up creating a ton of duplicated code. So I'm accepting his. But thank you! – Omnifarious Apr 04 '12 at 19:27
2

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.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • That's clever. I wouldn't have thought to use `enable_if` in that way, and I should've. I wanted a way to make substitution failure accomplish my goal, but I couldn't think how to do it. – Omnifarious Apr 03 '12 at 04:59
  • `return restore(std::move(val_));` gives me errors about returning a value from a void function. :-/ I was worried about that. – Omnifarious Apr 04 '12 at 07:31
  • @Omnifarious Can you produce a minimal example? The correct overload of `restore` gets picked up on my end. – Luc Danton Apr 04 '12 at 08:41
  • Oops, I missed putting in theoverload. _sheepish look_ It works. – Omnifarious Apr 04 '12 at 14:37
  • Both answers are interesting and help with different aspects of the problem. But this deals with the most important issue, which is how to handle `void` more sanely. So I'm accepting it. – Omnifarious Apr 04 '12 at 19:25
  • BTW, here is where the results of this question ended up: https://bitbucket.org/omnifarious/sparkles/changeset/4959ad268238 It could still be better, but this is a lot nicer and more convenient than what I had before. – Omnifarious Apr 05 '12 at 04:34