Recursively folding a parameter pack to resolve placeholder types

不打扰是莪最后的温柔 提交于 2019-12-24 10:37:49

问题


Notice: Followup to this question

After asking this question about parameter pack folding into pairs, I noticed that I need to retain the complete type of the previously folded type as the left pair type.

For example:

Fold<char, int, long, double> f;

must evaluate to

std::tuple<
    std::pair<char
             , int>,
    std::pair<std::pair<char, int> /* <-- the previous resulting type */
             , long>,
    std::pair<std::pair<std::pair<char, int>, long> /* the previous type again */
             , double>
> f;

Context to this problem

The reason why I need this, is because the types which have to be "folded" can be placeholder types. The placeholders "real" type can only be known when both having the fully expanded type to the left as well as the unexpanded type. The leftmost type never contains placeholders.

Let me illustrate this with a quick example:

struct CopyTypeFromPreviousArgumentTag { };

template<typename T = CopyTypeFromPreviousArgumentTag>
struct Foo;

template<typename T...>
struct Bar {
    /* Here fold will not use std::pair, but a resolver type that takes both A and B and gives back the resolved B */
    Fold<T...> expanded;
};

Now Bar can be used like this:

Bar< Foo<int>
   , Foo<>
   , Foo<>
   , Foo<double>
   , Foo<>
   > f;

and the internal type decltype(f::expanded) will be:

std::tuple< Foo<int>
          , Foo<int>
          , Foo<int>
          , Foo<double>
          , Foo<double>
          >;

EDIT: The Bar class is actually not restricted to any class type it might hold. It can be a mixture of several types. So see the Foo class as a placeholder for some type Foo where a resolver type traits exists given the previous resolved type: ResolveType<PreviouslyResolvedType, CurrentType>::Type will give the resolved type correctly. Hence the std::pair idiom.

My current attempt

I tried to implement the recursion by building upon the answer from the linked question, but can't get it to work.

namespace Detail {

template<typename, typename...>
struct Fold;

template
    < size_t... Indices
    , typename... Types
> struct Fold<std::index_sequence<Indices...>, Types...> {
    using Tuple = std::tuple<Types...>;
    using Type = std::tuple<std::pair /* use std::pair just to match the first example */
        //< std::tuple_element_t<Indices, Tuple>
        < typename Fold
            < std::tuple_element_t<Indices, Tuple>
            , std::make_index_sequence<Indices>
            , Types...>::Type; /* Tuple can't be expanded :( */
        , std::tuple_element_t<Indices + 1, Tuple>
        >::Type...>;
};

} /* namespace Detail */


template<typename... Types>
using Fold = typename Detail::Fold<std::make_index_sequence<sizeof...(Types) - 1>, Types...>::Type;

回答1:


I start my solution by building something that knows how to create the nth type. It's just a pair of the previous created type, and the current source type, so:

template <std::size_t I, class T>
struct element {
  using type = std::pair<typename element<I - 1, T>::type,
                         std::tuple_element_t<I + 1, T>>;
};

template <class T>
struct element<0, T> {
  using type =
      std::pair<std::tuple_element_t<0, T>, std::tuple_element_t<1, T>>;
};

All we really need to do now is put the input types into a tuple, grab an integer pack, and feed the tuple and unfold the integer pack through element, into a new tuple. No problem:

template <class I, class... Ts>
class fold_helper;

template <std::size_t... Is, class... Ts>
class fold_helper<std::index_sequence<Is...>, Ts...> {

  using tup = std::tuple<Ts...>;

public:
  using type = std::tuple<typename element<Is, tup>::type...>;
};

template <class... Ts>
using Fold = typename fold_helper<std::make_index_sequence<sizeof...(Ts)-1>,
                                  Ts...>::type;

Finally, let's check this works:

int main() {

  static_assert(
      std::is_same<Fold<char, int, long, double>,
                   std::tuple<std::pair<char, int>, //
                              std::pair<std::pair<char, int>, long>,
                              std::pair<std::pair<std::pair<char, int>, long>,
                                        double>>>::value,
      "");

  return 0;
};

This program compiled for me.




回答2:


The linked question is a very convoluted way of doing this. If it was a runtime problem, it will obviously be solved with a one-pass algorithm, metaprogramming is no different.

struct copy_prev {};

template<typename T = copy_prev>
struct Foo {};

template<typename... Ts, typename T>
auto operator+(std::tuple<Ts...>, Foo<T>)
    -> std::tuple<Ts..., Foo<T>>;

template<typename... Ts>
auto operator+(std::tuple<Ts...> t, Foo<copy_prev>)
    -> std::tuple<Ts..., select_last_t<Ts...>>;

template<typename... Ts>
using fold_t = decltype((std::tuple<>{} + ... + std::declval<Ts>()));

Where select_last_t is implemented as

template<typename T>
struct tag
{
    using type = T;
};

template<typename... Ts>
struct select_last
{
    using type = typename decltype((tag<Ts>{}, ...))::type;
};

template<typename... Ts>
using select_last_t = typename select_last<Ts...>::type;

Live




回答3:


Not sure to understand what do you want... but if your Bar struct accept only Foo types... that is: if can be written as follows

template <typename ...>
struct Bar;

template <typename ...Ts>
struct Bar<Foo<Ts>...>
 { /* something */ };

and in Bar you want a type so that from

 Bar<Foo<int>, Foo<>, Foo<>, Foo<double>, Foo<>>

you want the internal type

 std::tuple<Foo<int>, Foo<int>, Foo<int>, Foo<double>, Foo<double>>

where the unexpressed (defaulted) argument for Foo are substituted with the last expressed argument... I don't see an elegant solution.

The best I can imagine is the development of a type helper as follows (where, for the sake of brevity, I've renamed ctfpat the former CopyTypeFromPreviousArgumentTag)

template <typename...>
struct fooFolder;

// error case: the first type of Bar is ctfpat (non impemented;
// generate compile time error)
template <typename ... Ts>
struct fooFolder<std::tuple<>, ctfpat, ctfpat, Ts...>;

template <typename ... Tps, typename Tprev, typename T0, typename ... Ts>
struct fooFolder<std::tuple<Tps...>, Tprev, T0, Ts...>
   : fooFolder<std::tuple<Tps..., Foo<T0>>, T0, Ts...>
 { };

template <typename ... Tps, typename Tprev, typename ... Ts>
struct fooFolder<std::tuple<Tps...>, Tprev, ctfpat, Ts...>
   : fooFolder<std::tuple<Tps..., Foo<Tprev>>, Tprev, Ts...>
 { };

template <typename Tpl, typename Tprev>
struct fooFolder<Tpl, Tprev>
 { using type = Tpl; };

and Bar become

template <typename ...>
struct Bar;

template <typename ...Ts>
struct Bar<Foo<Ts>...>
 {
   using foldedType = typename fooFolder<std::tuple<>, ctfpat, Ts...>::type;

   foldedType expanded;
 };

The following is a full compiling example

#include <tuple>
#include <type_traits>

struct ctfpat // copy type from previous argument tag
 { };

template <typename T = ctfpat>
struct Foo
 { };

template <typename ...>
struct fooFolder;

// error case: the first type of Bar is ctfpat (non impemented;
// generate compile time error)
template <typename ... Ts>
struct fooFolder<std::tuple<>, ctfpat, ctfpat, Ts...>;

template <typename ... Tps, typename Tprev, typename T0, typename ... Ts>
struct fooFolder<std::tuple<Tps...>, Tprev, T0, Ts...>
   : fooFolder<std::tuple<Tps..., Foo<T0>>, T0, Ts...>
 { };

template <typename ... Tps, typename Tprev, typename ... Ts>
struct fooFolder<std::tuple<Tps...>, Tprev, ctfpat, Ts...>
   : fooFolder<std::tuple<Tps..., Foo<Tprev>>, Tprev, Ts...>
 { };

template <typename Tpl, typename Tprev>
struct fooFolder<Tpl, Tprev>
 { using type = Tpl; };

template <typename ...>
struct Bar;

template <typename ... Ts>
struct Bar<Foo<Ts>...>
 {
   using foldedType = typename fooFolder<std::tuple<>, ctfpat, Ts...>::type;

   foldedType expanded;
 };

int main()
 {
   using t1 = typename Bar<Foo<>, Foo<int>, Foo<>, Foo<double>,
                           Foo<>>::foldedType;
   using t2 = std::tuple<Foo<int>, Foo<int>, Foo<int>, Foo<double>,
                         Foo<double>>;

   static_assert( std::is_same<t1, t2>{}, "!" );
 }


来源:https://stackoverflow.com/questions/48967213/recursively-folding-a-parameter-pack-to-resolve-placeholder-types

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!