【问题标题】:How can I pull variadic template arguments off from the tail instead of the head?如何从尾部而不是头部拉出可变参数模板参数?
【发布时间】:2011-10-01 17:32:06
【问题描述】:

出于愚蠢的原因,我不会在这里讨论,我需要注释掉的行才能工作,而它上面的行不能工作:

template<uint _N, typename... _Args>
struct PartialTuple;

template<uint _N, typename _Arg, typename... _Args>
struct PartialTuple<_N, _Arg, _Args...>: PartialTuple<_N-1, _Args...> {};

template<typename _Arg, typename... _Args>
struct PartialTuple<0, _Arg, _Args...>
{
    typedef std::tuple<_Arg, _Args...> type;
};

int main()
{
    // I want this to not work...
    PartialTuple<1, std::string, std::string, int, int>::type A{"test", 5, 1};

    // I want this to work...
    //PartialTuple<1, std::string, std::string, int, int>::type B{"test", "test", 5};
}

我尝试将_Arg_Args... 交换,但这不会编译(至少在 GCC 4.6 中):

error: parameter pack argument ‘_Args ...’ must be at the end of the template argument list

如何从尾部而不是头部拉出物品?

【问题讨论】:

  • 我想你可以编写一个 veriadic 模板构造函数,并编写一个 Expand 辅助对象,如果大小太小,它会附加默认构造的对象,然后将整个 shebang 转发到另一个刚刚初始化的构造函数一切正常。
  • 快速备注:以_[A-Z] 开头的标识符在所有范围内都是保留的,您的代码不符合标准。
  • 很高兴知道,我会改变它。我从查看 STL 标头中养成了这个习惯,这可能是个坏主意。
  • @Sydius 确实是个坏主意,因为各种类型的保留标识符都是为标准库和其他实现细节保留的用于......此外,任何标准库实现并不意味着被视为示例代码(它希望具有示例的正确性/性能,但出于多种原因,它的风格不是用户应该模仿的)

标签: c++ c++11 variadic-templates


【解决方案1】:

我使用 Boost.MPL 和 Boost.Fusion 做了类似的事情:使用 MPL 工具(例如 push_back)计算类型序列,然后使用 fusion::as_vector 和 MPL 适配器将其转换为 fusion::vector。不过,我已经有一个助手可以将 fusion::vector 转换为 std::tuple

【讨论】:

    【解决方案2】:

    我整晚都在玩它,终于找到了一些东西(改变了我的外壳以匹配 STL):

    template<uint _N, typename... _All>
    struct reverse_tuple_outer
    {
        template<typename _Head, typename... _Tail>
        struct reverse_tuple_inner: reverse_tuple_outer<_N-1, _Head, _All...>::template reverse_tuple_inner<_Tail...> { };
    };
    
    template<typename... _All>
    struct reverse_tuple_outer<0, _All...>
    {
        template<typename... _Tail>
        struct reverse_tuple_inner {
            typedef std::tuple<_All...> type;
        };
    };
    
    template<typename... _Args>
    struct reverse_tuple
    {
        typedef typename reverse_tuple_outer<sizeof...(_Args)>::template reverse_tuple_inner<_Args...>::type type;
    };
    
    template<typename... _Args>
    struct strip_and_reverse_tuple;
    
    template<typename... _Args>
    struct strip_and_reverse_tuple<std::tuple<_Args...>>
    {
        typedef typename reverse_tuple<_Args...>::type type;
    };
    
    template<uint _N, typename... _Args>
    struct partial_tuple
    {
        typedef typename strip_and_reverse_tuple<typename reverse_tuple_outer<sizeof...(_Args)-_N>::template reverse_tuple_inner<_Args...>::type>::type type;
    };
    
    int main()
    {
        //partial_tuple<1, std::string, std::string, int, int>::type A{"test", 5, 1};
        partial_tuple<1, std::string, std::string, int, int>::type B{"test", "test", 5};
    }
    

    另外,我还有reverse_tuple,如果我需要的话。

    【讨论】:

    • 您不应该将 STL 与大小写“匹配”,它是为实现而保留的。
    • 哦,这很好。那么,我将在我的代码中将其改回。 :-)
    【解决方案3】:

    我让我的代码工作起来有点像 Haskell 中的列表 - 因为,嗯,TMP 是 C++ 中的纯函数式语言。

    add_to_pack 等价于 Haskell 的列表构造函数 (:)drop_from_end 实现为(在 Haskell 表示法中)\x list -&gt; take (length list - x) list,其中 take n 只取列表的第一个 n 元素。

    我想你可以直接使用std::tuple 而不是pack,但我更喜欢这个解决方案,因为它不会滥用元组作为模板参数包持有者。 :)

    代码如下:

    #include <tuple>
    #include <type_traits> // for std::conditional
    
    
    template <typename... Pack>
    struct pack
    { };
    
    
    template <typename, typename>
    struct add_to_pack;
    
    template <typename A, typename... R>
    struct add_to_pack<A, pack<R...>>
    {
      typedef pack<A, R...> type;
    };
    
    
    template <typename>
    struct convert_to_tuple;
    
    template <typename... A>
    struct convert_to_tuple<pack<A...>>
    {
      typedef std::tuple<A...> type;
    };
    
    
    template <int, typename...>
    struct take;
    
    template <int N>
    struct take<N>
    {
      typedef pack<> type;
    };
    
    template <int N, typename Head, typename... Tail>
    struct take<N, Head, Tail...>
    {
      typedef
        typename std::conditional<
          (N > 0),
          typename add_to_pack<
            Head,
            typename take<
              N - 1,
              Tail...
            >::type
          >::type,
          pack<>
        >::type type;
    };  
    
    
    template <int N, typename... A>
    struct drop_from_end
    {
      // Add these asserts if needed.
      //static_assert(N >= 0,
      //  "Cannot drop negative number of elements!");
    
      //static_assert(N <= static_cast<int>(sizeof...(A)),
      //  "Cannot drop more elements than size of pack!")
    
      typedef
        typename convert_to_tuple<
          typename take<
            static_cast<int>(sizeof...(A)) - N,
            A...
          >::type
        >::type type;
    };
    
    
    int main()
    {
      drop_from_end<2, const char*, double, int, int>::type b{"pi", 3.1415};
    }
    

    下面是工作代码:via ideone.com


    take 结构或多或少等同于以下 Haskell 代码:

    take n []     = []
    take n (x:xs)
      | n > 0     = x : take (n - 1) xs
      | otherwise = []
    

    【讨论】:

    • 我喜欢你使用pack 的想法。我没有在 TMP 和函数式编程之间建立联系,可能是因为我从来没有花太多时间在纯函数式语言上。有趣的见解。
    【解决方案4】:

    这是一个解决方案:我没有从后面截断N,而是从前面截断sizeof...(Args) - N

    #include <tuple>
    
    /* Concatenator helper */
    
    template <typename T, typename Tuple> struct cat;
    template <typename T, typename ...Args>
    struct cat<T, std::tuple<Args...>>
    {
      typedef typename std::tuple<T, Args...> value;
    };
    
    
    /* Head-of-tuple */
    
    template <unsigned int, typename...> struct tuple_head;
    
    // Base case. Need to specialize twice, once for one and once for variadic types
    template <typename ...Args>
    struct tuple_head<0, Args...>
    {
      typedef std::tuple<> value;
    };
    template <typename T>
    struct tuple_head<0, T>
    {
      typedef std::tuple<> value;
    };
    
    // Recursion step
    template <unsigned int N, typename T, typename ...Args>
    struct tuple_head<N, T, Args...>
    {
      typedef typename cat<T, typename tuple_head<N - 1, Args...>::value>::value value;
    };
    
    
    /* User interface */
    
    template <unsigned int N, typename ...Args>
    struct PartialTuple
    {
      typedef typename tuple_head<sizeof...(Args) - N, Args...>::value type;
    };
    
    
    /* Usage */
    
    #include <string>
    int main()
    {
      // I want this to not work...
      //PartialTuple<1, std::string, std::string, int, int>::type A{"test", 5, 1};
    
      // I want this to work...
      PartialTuple<1, std::string, std::string, int, int>::type B("test", "test", 5);
      PartialTuple<0, std::string, std::string, int, int>::type C("test", "test", 5, 6);
    }
    

    【讨论】:

    • 看来我们的想法一样! +1 :)
    • 当你这样说的时候,似乎很明显。 :-)
    猜你喜欢
    • 2018-07-31
    • 2016-12-01
    • 1970-01-01
    • 2023-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-02
    • 2012-01-14
    相关资源
    最近更新 更多