【问题标题】:Defining element initialization order when constructing std::tuple在构造 std::tuple 时定义元素初始化顺序
【发布时间】:2018-03-21 18:57:24
【问题描述】:

我想将元组中元素的初始化值存储在一个单独的元组中,以便我可以将相同的值用作相应类型的其他元组的预设值。

元组元素的构造函数以从左到右的顺序调用对我的程序非常重要(否则它充其量只会变得非常混乱)。

这是我的程序的简化版本:

#include <tuple>

// Elements are the end points of a Widget hierarchy
struct Element
{
    using initer_t = int;
    Element( const initer_t pIniter )
        :data{ pIniter }
    {
        printf("Creating %i\n", data);
    }
    const initer_t data;
};

// A Widget class stores any number of Elements and/or other Widget instances
template<typename... Elems>
    struct Widget
    {
        using initer_t = std::tuple<typename Elems::initer_t...>;
        Widget( const initer_t pIniter )
            :elements{ pIniter } 
        {}
        const std::tuple<Elems...> elements;
    };

int main()
{
    using Button = Widget<Element, Element>;
    using ButtonList = Widget<Button, Button, Element>;

    Button::initer_t basic_button_initer{ 0, 1 }; // presets for Buttons
    Button::initer_t other_button_initer{ 2, 3 }; 

    ButtonList::initer_t buttonlist_initer{ basic_button_initer, other_button_initer, 4 }; //a preset for a ButtonList

    ButtonList buttonlist{ buttonlist_initer };
    return 0;
}

所以我在Widget&lt;Elems...&gt; 的构造函数初始化列表中使用std::tuple&lt;typename Elems::initer_t...&gt; 初始化Widget&lt;Elems...&gt;std::tuple&lt;Elems...&gt; elements 成员。 这应该使用 pIniter 中的值使用 initer_t 定义的类型的相应初始化值来初始化 elements 的每个元素。 initer_t 类型是 Widget 层次结构中每个成员的类型(例如 Widget&lt;typename...&gt;Element),这是层次结构成员应该初始化的类型。 但是发生这种情况的顺序是从右到左,而我需要从左到右。

程序的输出是

Creating 4
Creating 3
Creating 2
Creating 1
Creating 0

但我想颠倒这个顺序。

在这种情况下我该怎么做?

【问题讨论】:

  • 即使这个订单也不能保证。
  • 从 g++ 我得到 "4 3 2 1 0",从 clang++ "0 1 2 3 4"
  • 我刚刚用 clang 和 clang++ 编译它,我得到 "4 3 2 1 0" ...
  • 为什么您需要特定的订单?是用来记录的吗?
  • @stimulate "我只是用clang编译的" 不是有用的信息,因为这里重要的是std::tuple的实现。所以使用 Clang 和 libstdc++ 将给出与 GCC 相同的结果(因为它使用相同的 std::tuple)。使用 Clang 和 libc++ 不会。

标签: c++ templates constructor tuples order-of-execution


【解决方案1】:

恐怕标准中对std::tuple成员初始化的顺序没有要求。

您可以按特定顺序迭代 tuple,例如:

#include <tuple>
#include <iostream>

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>

int main()
{
    auto a = std::make_tuple(true, 42, 3.14, "abc");
    boost::fusion::for_each(a, [](auto& value) {
            std::cout << value << '\n';
        });
}

输出:

1
42
3.14
abc

【讨论】:

  • :( 所以这意味着我必须退回到一个一个地初始化每个成员,这意味着我不能使elements const,这使得整个事情变得不那么优雅和高性能..?对 C++ 的失望...
【解决方案2】:

对于任何对解决方案感兴趣的人,我想出了一种方法来控制初始化顺序并保留elements 的常量:

#include <tuple>

template<typename... Elems>
    struct construct 
    {
        template<size_t... Ns, typename Head, typename... Rest>
            static constexpr const std::tuple<Rest...> 
                drop_head_impl( const std::index_sequence<Ns...> ns, 
                    const std::tuple<Head, Rest...> tup )
            {
                return std::tuple<Rest...>( std::get<Ns + 1u>( tup )... );
            }

        template<typename Head, typename... Rest>
            static constexpr const std::tuple<Rest...> 
                drop_head( const std::tuple<Head, Rest...> tup )
            {
                return drop_head_impl( std::make_index_sequence<sizeof...(Rest)>(), tup );
            }

        template<typename Head>
            static constexpr const std::tuple<Head> 
                func_impl( const std::tuple<typename Head::initer_t> initer )
            {
                return  std::tuple<Head>( { std::get<0>( initer ) } ); 
            }

        template<typename Head, typename Next, typename... Rest>
            static constexpr const std::tuple<Head, Next, Rest...> 
                func_impl( const std::tuple<typename Head::initer_t, typename Next::initer_t, typename Rest::initer_t...> initer )
            {
                std::tuple<Head> head( { std::get<0>( initer ) } ); 
                return std::tuple_cat( head, func_impl<Next, Rest...>( drop_head(initer) ) );
            }

        static constexpr const std::tuple<Elems...> 
            func( const std::tuple<typename Elems::initer_t...> initer )
        {
            return func_impl<Elems...>( initer );
        }
    };

// Elements are the end points of a Widget hierarchy
struct Element
{
    using initer_t = int;
    Element( const initer_t pIniter )
        :data{ pIniter }
    {
        printf( "Creating %i\n", data );
    }
    const initer_t data;
};

// A Widget class stores any number of Elements and/or other Widget instances
template<typename... Elems>
    struct Widget
    {
        using initer_t = std::tuple<typename Elems::initer_t...>;
        Widget( const initer_t pIniter )
            :elements( construct<Elems...>::func( pIniter ) ) 
        {}
        const std::tuple<Elems...> elements;
    };

int main()
{
    using Button = Widget<Element, Element>;
    using ButtonList = Widget<Button, Button, Element>;

    Button::initer_t basic_button_initer{ 0, 1 }; // presets for Buttons
    Button::initer_t other_button_initer{ 2, 3 }; 

    ButtonList::initer_t buttonlist_initer{ basic_button_initer, other_button_initer, 4 }; //a preset for a ButtonList

    ButtonList buttonlist{ buttonlist_initer };
    return 0;
}

construct 结构接受initer_ts 的元组(initer),使用initer 的第一个元素构造一个包含Elems... 的第一个元素的元组,然后丢弃initer 的第一个元素并传递剩余的元组到它自己,这会导致使用 initer 中的下一个元素构造具有 Elems... 的下一个元素的元组。此递归通过 func_impl 的重载来停止,该元组具有一个元素,该元素简单地从元组中的 initer_t 构造该元素并返回它。这个单元素元组连接到与前一个元素的元组,结果返回到更高级别并连接到那里的单元素元组等等。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-07
    • 1970-01-01
    • 2012-03-19
    相关资源
    最近更新 更多