C ++ 14类型列表,任何理由更喜欢“自由函数”到“方法”,反之亦然?

时间:2015-06-12 18:04:09

标签: c++ c++11 c++14 template-meta-programming boost-mpl

我看到在C ++ 11/14中实现类型列表有两种可能的样式,我很好奇是否有任何理由偏爱另一种。第一种技术是outlined here,并在Boost的MPL库中进行建模。在这种风格中,您可以定义元“自由函数”(使用声明的顶级),它们接收类型列表并对其进行操作。以下是如何实现std :: transform的元版本,它适用于类型而不是第一种样式中的值:

    template <typename... Args>
    struct type_list;

    namespace impl
    {
        template <template <typename...> class F, class L>
        struct transform_impl;

        template <template <typename...> class F, template <typename...> class L, typename... T>
        struct transform_impl<F, L<T...>>
        {
            using type = L<typename F<T>::type...>;
        };
    }

    template <template <typename...> class F, class L>
    using transform = typename impl::transform_impl<F, L>::type;

第二种方式是定义元'方法'(使用类型列表结构中的声明)。以下是变换在该样式中的外观:

    template <typename... Args>
    struct type_list {
        // ... other 'methods'

        template<template<class> class Wrapper>
        using transform =
            type_list<Wrapper<Args>...>;

        // ... other 'methods'
    };

我在第二种风格中看到的优势是您仍然可以使用Args...参数包,因此您不必委托impl辅助函数。两个可能的缺点是:1)你必须将所有元函数放在type_list中,而不是将它们放在单独的头文件中,这样你就失去了一些模块性; 2)'free'元函数也可以用于元组和任何其他可变参数模板开箱即用。我不知道#2实际上对实际的渴望有多普遍,我只发现自己使用type_list和tuple的时候,编写元代码以在type_list和tuple之间进行转换并不困难。

是否有充分的理由强烈倾向于选择其中一个?也许#2实际上是常见的情况?

1 个答案:

答案 0 :(得分:9)

第二个因为很多原因而不好。

首先,称它为一团糟。模板内的模板需要使用template关键字。

其次,它要求您的类型列表包含您要在其正文中的类型列表上执行的每个操作。就像在string上定义每个操作作为字符串上的方法一样:如果允许自由函数,则可以创建新操作,甚至可以实现覆盖。

最后,考虑隐藏::type

从这些原语开始:

template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;
template<class...Ts>struct types : tag<types<Ts...>>{};

转换,或fmap,然后看起来像:

template<template<class...>class Z, class Types>
struct fmap;
template<template<class...>class Z, class...Ts>
struct fmap<Z, types<Ts...>>:types<Z<Ts...>>{};
template<template<class...>class Z, class Types>
using fmap_t = type_t<fmap<Z,Types>>;

您可以使用type_t<fmap<Z,types<int,double>>>fmap_t<Z,types<int,double>>来获取映射类型的类型。

另一种方法是使用包含各种内容的constexpr函数:

template<class T>struct tag{using type=T;};
template<class...>struct types{using type=types;};
template<class Tag>using type_t=typename Tag::type;

template<template<class...>class Z>
struct z {template<class...Ts>using apply=Z<Ts...>; constexpr z(){};};
template<class...Ts>
struct one_type {};
template<class T0>
struct one_type<T0> { using type=T0; };
template<class...Ts>
using one_type_t=typename one_type<Ts...>::type;

template<template<class>class Z>
struct z_one_base {
    template<class...Ts>
    using helper = Z<one_type_t<Ts...>>;
    using type = z<helper>;
};
template<template<class>class Z>
using z_one = type_t<z_one_base<Z>>;

现在fmap只是:

// take a template metafunction and a list of types
// and apply the metafunction to each type, returning the list
template<template<class...>class Z, class...Ts>
constexpr auto fmap( z<Z>, types<Ts...> )
-> types<Z<Ts>...> { return {}; }

和其他功能如下:

// a template metafunction and a list of types
// and apply the template metafunction to all of the types
template<template<class...>class Z, class...Ts>
constexpr auto apply( z<Z>, types<Ts...> )
-> tag<Z<Ts...>> { return {}; }

// take any number of tags
// and make a type list from them
template<class...Tags>
constexpr auto make_list( Tags... )
-> types<type_t<Tags>...> { return {}; }

// concat of nothing is an empty list
constexpr types<> concat() { return {}; }
// concat of a list alone is a list alone:
template<class...T1s>
constexpr auto concat(types<T1s...>)
->types<T1s...>{ return {}; }
// concat of 2 or more lists is the concat of the first two,
// concatted with the rest
template<class...T1s, class...T2s, class...Types>
constexpr auto concat(types<T1s...>,types<T2s...>,Types...)
->decltype( concat(types<T1s...,T2s...>{},Types{}...) )
{ return {}; }


// take a tagged list or a tagged type, and return a list
template<class T>
constexpr auto fbox( tag<T> )->types<T> { return {}; }
template<class...Ts>
constexpr auto fbox( tag<types<Ts...>> )->types<Ts...> { return {}; }

// create z_ versions of functions above:
#define CAT2(A,B) A##B
#define CAT(A,B) CAT2(A,B)
// lift functions to metafunctions with z_ prefix:
#define Z_F(F) \
  template<class...Ts> \
  using CAT(meta_, F) = decltype( F( Ts{}... ) ); \
  using CAT(CAT(z_, F),_t) = z<CAT(meta_, F)>; \
  static constexpr CAT(CAT(z_, F),_t) CAT(z_, F){}

Z_F(concat);
//Z_F(apply);
//Z_F(fmap);
Z_F(fbox);
static constexpr z_one<tag> z_tag{};


// joins a list of lists or types into a list of types
template<class...Ts>
constexpr auto join1(types<Ts...>)
->type_t<decltype( apply( z_concat, fmap( z_fbox, types<tag<Ts>...>{} ) ) )>
{ return {}; }
template<class Types>
constexpr auto join(Types types)
->type_t<decltype( apply( z_concat, fmap( z_fbox, fmap( z_tag, types ) ) ) )>
{ return {}; }

template<class Z, class...Ts>
constexpr auto fbind(Z z, Ts...ts)
->decltype( join( fmap( z, ts... ) ) )
{ return {}; }

使用psuedo-types(tag s)而不是直接在顶层使用类型。如果您需要,可以使用type_t提升回类型。

我认为这是一种类似boost::hana的方法,但我只是开始关注boost::hana。这里的优点是我们将类型包与操作分离,我们可以访问完整的C ++重载(而不是模板模式匹配,这可能更脆弱),我们可以直接推断出类型包的内容而不必执行using和空主要专业化技巧。

消费的一切都是tag<?>types<?>z<?>的包装类型,所以没有什么是&#34;真实&#34;。

测试代码:

template<class T> using to_double = double;
template<class T> using to_doubles = types<double>;

int main() {
    types< int, int, int > three_ints;

    auto three_double = fmap( z_one<to_double>{}, three_ints );
    three_double  = types<double, double, double >{};
    auto three_double2 = join( fmap( z_one<to_doubles>{}, three_ints ) );
    three_double = three_double2;
    auto three_double3 = fbind( z_one<to_doubles>{}, three_ints );
    three_double3 = three_double2;
}

Live example