【问题标题】:Create an optional tuple创建一个可选元组
【发布时间】:2015-06-09 13:08:06
【问题描述】:

我不明白为什么没有optional tuple,我的意思是,像这样的东西; optional<int,string,char> 将结合 optional intoptional stringoptional char

你可以把它想象成一个优化的std::tuple<boost::optional<T>...>

optionals 使用的booleans 将全部放在structure 的末尾,以便打包它,或者更好的是,存储在位集中。

这可能会减少结构的内存 A LOT,也更优雅:

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

VS

optional<int,string,char>

我可以想到一种方法,使用boost::optionalvariadic templates的实现,但在开始之前,我想知道这是否是一个好主意,什么是更好的实现方式这个,我会遇到什么困难?

编辑:

基本上我为什么不喜欢std::tuple<boost::optional<T>...>;

因为optional<T>Tbool 的联合:

新结构可以节省大量内存!!

【问题讨论】:

  • 我知道您说的是可选的 boost,但 C++17“词汇”类型仍在设计中。如果你认为 std::optional 可以使用,你应该向 C++ 委员会提交提案
  • 另外,也许您可​​以定义自己的“可选”模板别名以及用于声明和构造可变参数可选的工厂函数。
  • @KABoissonneault 我正在考虑类似的事情,但我不确定optionallike move'ing 数据不会有任何问题
  • @AlexandreC。 “可选元组与可选元组不同”,每个人都同意这一点,“你的提议相当于为 std::tuple<optional<Ts>...> 编写专业化” 不是专业化,可以作为 std::tuple<optional<Ts>...> 工作但内存较少
  • @AlexandreC。 :我们已经看到std::vector<bool> 专业化发生了什么。这并不漂亮,尽管位向量非常有用。我真的认为std::tuple<X,Y,Z> 应该包含 X、Y 和 Z 类型的成员,即使 X 恰好是 optional<int>

标签: c++ boost tuples variadic-templates optional


【解决方案1】:

您可以自己实现,类似于:

/**
 *  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

【讨论】:

  • 感谢您的实施,这是一项不错的工作。但我仍然想知道使用它是否比std::tuple&lt;boost::optional&lt;T&gt;...&gt;; 更有效?? (不管获得的记忆)
  • @OthmanBenchekroun:正如您在架构中解释的那样,您获得了一些填充空间(Ts 的顺序在这方面也很重要)。你也可以用更紧凑的结构(std::bitset)替换我的array&lt;bool, N&gt;。为了提高效率,由于bool 不再靠近它的data,它对缓存的友好性较低(现在它还取决于multi_optional 中的类型数量)。
  • 好的,我知道了,我会尝试使用你的实现做一些测试
  • @Jarod42:“缓存不友好”?我不确定。如果boolfalse,则没有可接近的数据。
  • @MSalters:我认为这在极端情况下可能是个问题(lot of Ts),但在大多数情况下应该忽略。
【解决方案2】:

使用模板别名不是一个好选择吗?

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

或者,如果您愿意,实际上是其他变体;

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

现在你可以写了

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

并获得您选择的含义。

当然,如果只有一个元组成员被填充,这已经存在:boost::variant&lt;T...&gt;

【讨论】:

  • 感谢您的回答,但正如我解释的那样,我真的不想使用std::tuple&lt;boost::optional&lt;T&gt;...&gt;; 由于数据之间的boolean,该结构有很多填充
  • @sehe 也不是只有一个成员被填满,他们可能都被填满了
  • 啊。我想您确实可以打包它(例如using optuple = std::tuple&lt;std::bitset&lt;sizeof...(T)&gt;, std::tuple&lt;T...&gt; &gt;;,但随后使用适当的惰性就地构造和逻辑。)。是的,这是更多的工作。
【解决方案3】:

您想要的结构已经可以创建为tuple&lt;T&gt; 的特化。引入额外的结构没有任何意义。与制作一个特殊的vector 只是为了雇用 EBO 相比,这里没有什么不同。

现有界面可以很好地满足您的需求。如果你想要另一个实现,我猜你总是可以专门化或推出你自己的。

【讨论】:

  • 我认为(假设 std::optional 已经存在)部分专门针对 template &lt;typename... Ts&gt; class std::tuple&lt;std::optional&lt;Ts&gt;...&gt; 是非法的。按照通常的规则,std 命名空间中的特化需要针对用户定义的类型?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多