【问题标题】:How do I make a simplified version of boost::variant?如何制作 boost::variant 的简化版本?
【发布时间】:2012-04-16 17:07:22
【问题描述】:

我有一套更简单的要求,不需要太多变体的机器。如果我能帮上忙,我也不想依赖 boost。

我需要存储编译时已知的任意类型(可能是void)。它要么是可移动构造的,要么是可复制构造的,如果其中任何一个抛出,则允许包含的值未定义。

除了这个值,它还可以包含::std::exception_ptr 或::std::error_code。

如果T 被允许无效,::boost::variant<T, ::std::exception_ptr, ::std::error_code> 将起作用。除了::boost::variant 提供了我在这种情况下实际上不需要的“永不空保证”。而且,如果我理解它是如何正常工作的,它与可以移动但不能复制的类型不太兼容。

现在我正在编写大量重复的代码,我不应该编写这些代码来分别处理每种类型。我还存储了每种类型和标志值的对象的副本,说明哪些是相关的。最后,void 提供了整个系统的概念,并要求我在各处编写专业。

有没有更好的办法?

这是我所拥有的一个简化示例。这基本上是一个旨在保存传输到另一个线程的结果的类,有点像未来:

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_;
};

【问题讨论】:

  • 您可以以对用户透明的方式将void 映射到唯一的空类型。这将允许您重用boost::variant,但不需要专业化帮助。然而,boost::variant 最大的障碍是移动构造。
  • 听起来很像过早优化。
  • @LucDanton:点头 是的,我在另一个答案中看到了这一点。我想我可以理解它是如何工作的。
  • @Cheersandhth.-Alf:不,这是一个设计级别的问题。我一直在到处写这些重复的代码。由于很多不同的原因,这并不好。
  • 我知道如何回答你,但如果你不熟悉它,你最好看看std::aligned_storage。

标签: c++ templates c++11 void variant


【解决方案1】:

如何使用您现在拥有的代码以透明方式处理void 的示例:

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_;
};

虽然代码未经测试,但可能有一些问题。

【讨论】:

  • 这很聪明。我不会想到以这种方式使用enable_if,而且我应该这样做。我想要一种方法让替换失败实现我的目标,但我想不出该怎么做。
  • return restore(std::move(val_)); 给了我关于从 void 函数返回值的错误。 :-/ 我很担心。
  • @Omnifarious 你能举一个最小的例子吗? restore 的正确重载在我的最后被拾取。
  • 糟糕,我错过了超载。 笨拙的样子有效。
  • 这两个答案都很有趣,并且有助于解决问题的不同方面。但这涉及最重要的问题,即如何更理智地处理void。所以我接受了。
【解决方案2】:

[编辑:在我写完这个问题后,问题被编辑,添加了示例代码]。

它似乎你所追求的是一种从函数返回有效结果(任意类型)或指示失败的东西的方法。

如果是这样,那么下面的代码可以很好地解决您的问题,即返回任意类型结果的部分(这个类类似于boost::optional,它又基于 Barton 和 Nackman 的Fallible类)。

对于错误指示,只需将布尔值替换为错误信息,并将概念上的“无”替换为概念上的“失败”:

#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

【讨论】:

  • 是的,这有助于我思考问题。我现在明白了如何才能完成这项工作。将所有不同的值叠加在一起对我来说实际上并不那么重要。正如您所指出的,过早的优化。这很好地解决了剩下的问题。
  • 这是一种非常有趣的方法来处理可能实际上不存在的值。现在我依赖于默认可构造的值。但是,将来为仅可移动或仅可复制构造的值工作会很好。不幸的是,我现在最大的问题是理智地处理 void,而 Luc 有一个比我的解决方案更好的解决方案,它最终会创建大量重复的代码。所以我接受他的。不过谢谢!
猜你喜欢
  • 2017-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-26
  • 1970-01-01
相关资源
最近更新 更多