C 组合模板扩展

C++ combinatorial template expansion

本文关键字:扩展 组合      更新时间:2023-10-16

我想从库中请求一系列数据,这要求我指定两种类型:

class a;
class b;
class c;
template<typename A, typename B> void get() {....}
void get_all() {
  get<a,a>()
  get<a,b>()
  get<a,c>()
  get<b,a>()
  get<b,b>()
  // etc...
}

我想称呼get&lt; a,b&gt;一组类(大约10个不同的变体(的每种组合的功能 - 含义大约100个不同的调用。我宁愿不用手编码所有这些get((呼叫。

是否有某种方法可以自动生成这些调用?我想我可以使用预处理器宏,但是我很好奇是否有通过使用模板代码来产生这样的组合列表。

编辑:次要复杂性:实际上,这全都在成员函数中。

template<typename A, typename B> 
void Getter::get() 
{  
  auto assn = fMemberObject->RetrieveAllDataOfType<association<A,B>();
  .. do stuff with assn ...
}
void Getter::get_all() {
  get<a,a>();
}

因此,代码需要与模板一起处理传递this。另外,如果可能的话,C 11可能是我想推动语言的范围。

编辑2:原来我想避免重复的情况<a,a><b,b> ...

在解决模板元编程问题时,它有助于考虑如何在正常编程中执行此操作。在这种情况下,我们想要类似的东西:

for a in type_list:
    for b in type_list:
        foo(a,b)

因此,让我们将其直接翻译成C 。我们需要一种表示类型和打字员的方法:

template <class T> struct tag { };
template <class... Ts> struct type_list { };

我们需要一种在类型列表上迭代并为每种类型做某事的方法。我们将其称为功能调用。在C 17(在C 14及更低中,您可以使用燕子/扩展器技巧来调用每个tag<Ts>上的f(:

template <class... Ts, class F>
void for_each(type_list<Ts...>, F f) {
    (f(tag<Ts>{}), ...);
}

这实际上是……实际上。我们需要一个实际调用的函数,打字机和代码:

template <class X, class Y>
void foo(tag<X>, tag<Y> ) {
    std::cout << __PRETTY_FUNCTION__ << 'n'; // or something more meaningful
}
int main() {
    type_list<a, b, c> types;
    for_each(types, [=](auto x){
        for_each(types, [=](auto y){
            foo(x, y);
        });});
}

演示

C 11版本,演示通用lambdas的力量。


基于想要类型不不同的更新,这也很容易更新。我们可以将平等运算符添加到tag

template <class T, class U>
constexpr std::integral_constant<bool, std::is_same<T,U>::value>
operator==(tag<T>, tag<U> ) { return {}; }
template <class T, class U>
constexpr std::integral_constant<bool, !std::is_same<T,U>::value>
operator!=(tag<T>, tag<U> ) { return {}; }

请注意,我们没有返回bool,我们正在返回一种编码类型系统中结果的类型。然后,我们可以自然使用它:

for_each(types, [=](auto x){
    for_each(types, [=](auto y){
        if constexpr (x != y) {
            foo(x, y);
        }
    });});

foo(x,y)只有在两种标签类型不同的情况下才会实例化。

更新以处理不使用相同类型参数调用get()的新要求,并且get()是类成员函数。我在这里打电话给manager类。


您可以(AB(使用C 11变异模板来实现这一目标。可能有一种更简洁的方法可以做到这一点,但是它有效,并且(希望(可以理解:

struct manager
{
    // Sample implementation of get(), showing the names of the types.
    template <typename A, typename B>
    void get() {
        std::cout << "A=" << typeid(A).name() << " B=" << typeid(B).name() << 'n';
    }
};
// Implementation, uses variadic templates with recursion.
namespace detail
{
    // Helper to delimit template parameter packs.
    template <typename...> struct pack;
    // Terminating case, <=1 type argument(s) means we are done.
    template <typename...>
    struct call_get_all_beta
    {   
        static void call(manager &) { }
    };
    // Invoke get<First, Second>() and recurse with <First, Tail...>.
    template <typename First, typename Second, typename... Tail>
    struct call_get_all_beta<First, Second, Tail...>
    {   
        static void call(manager &m) {
            m.get<First, Second>();
            call_get_all_beta<First, Tail...>::call(m);
        }
    };
    // Specialization to handle skipping over types that are the same.
    template <typename First, typename... Tail>
    struct call_get_all_beta<First, First, Tail...>
    {   
        static void call(manager &m) {
            call_get_all_beta<First, Tail...>::call(m);
        }
    };
    template <typename...>
    struct call_get_all_alpha;
    // Terminating case, first pack is empty.
    template <typename... B>
    struct call_get_all_alpha<pack<>, pack<B...>>
    {   
        static void call(manager &) { }
    };
    // Pass <FirstA, B...> on to call_get_all_beta, recurse with
    // <pack<TailA...>, pack<B...>>.
    template <typename FirstA, typename... TailA, typename... B>
    struct call_get_all_alpha<pack<FirstA, TailA...>, pack<B...>>
    {   
        static void call(manager &m) {
            call_get_all_beta<FirstA, B...>::call(m);
            call_get_all_alpha<pack<TailA...>, pack<B...>>::call(m);
        }
    };
}
// Helper to call the implementation detail.
template <typename... Types>
void get_all_permutations(manager &m)
{
    detail::call_get_all_alpha<detail::pack<Types...>, detail::pack<Types...>>::call(m);
}

然后我们只做get_all_permutations<a, b, c>();。(演示(

要解释其工作原理,detail::call_get_all_alpha采用两个pack<...>模板参数,两者最初都包含整个类型集。第二个保持不变,但每种类型都会在每次递归时都从第一个包装上剥离。第一种类型和完整的类型集(通过第二个包(传递给detail::call_get_all_beta,它使用相同的"脱皮"递归技术将<A, B, C, D>变成呼叫get<A, B>()get<A, C>()get<A, D>()

因此,在较高级别上,detail::call_get_all_alpha负责迭代 first 模板参数到 get(),而 detail::call_get_all_beta负责迭代 second 模板参数。 template参数。

如果您可以使用Boost,则可以避免大量的基本打字机和应用程序样板,从而使代码更容易阅读。无需在这里重新发明轮子。

此示例使用boost.mpl,该示例具有广泛的编译器支持,但我敢肯定,boost.hana中的等效解决方案更漂亮。

首先,生成对的功能(根据此答案进行了改编(:

#include <utility>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/lambda.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/push_back.hpp>
// For each element in TList, append to Accumulator
// the type pair<PairFirst, element>.
template <
    typename TList, typename PairFirst, typename Accumulator = boost::mpl::vector<>>
struct generate_pairs_fixed_element :
    boost::mpl::fold<
        TList,
        Accumulator,
        boost::mpl::push_back<boost::mpl::_1, std::pair<PairFirst, boost::mpl::_2>>>
{};
// For each element in TList, concatenate to Accumulator
// the result of generate_pairs_fixed_element<TList, element>.
template <typename TList, typename Accumulator = boost::mpl::vector<>>
struct generate_pairs_combinations :
    boost::mpl::fold<
        TList,
        Accumulator,
        boost::mpl::lambda<
            generate_pairs_fixed_element<TList, boost::mpl::_2, boost::mpl::_1>
        >>
{};

现在只需将您的类型收集到列表中并生成组合:

class a {};
class b {};
class c {};
class d {};
typedef boost::mpl::vector<a, b, c, d> all_classes_t;
typedef generate_pairs_combinations<all_classes_t>::type all_classes_pairs_t;

并与他们一起做任何您想做的事:

#include <iostream>
#include <boost/mpl/for_each.hpp>
#include <boost/type.hpp>
template <typename A, typename B>
void get() {
    std::cout << "Getting " << typeid(A).name()
              << " and " << typeid(B).name() << std::endl;
}
struct pair_visitor {
    template <typename T, typename U>
    void operator()(boost::type<std::pair<T, U>>) const {
        get<T, U>();
    }
};
int main() {
    boost::mpl::for_each<all_classes_pairs_t, boost::type<boost::mpl::_>>(pair_visitor());
}

您可以使用以下内容:

class a;
class b;
class c;
template<typename A, typename B> void get() { /*....*/ }
namespace detail {
    template <typename> struct tag{};
    template <typename Tuple, std::size_t...Is>
    void get_all_pairs(tag<Tuple>, std::index_sequence<Is...>)
    {
        constexpr auto size = std::tuple_size<Tuple>::value;
#if 1 // Folding expression with C++17
        (get<std::tuple_element_t<Is / size, Tuple>,
             std::tuple_element_t<Is % size, Tuple>>(), ...);
#else
        const dummy[] = {0, (get<std::tuple_element_t<Is / size, Tuple>,
                                 std::tuple_element_t<Is % size, Tuple>>(),
                             void(), 0)...};
        static_cast<void>(dummy); // Avoid warning for unused variable.
#endif
    }
    template <typename ... Ts>
    void get_all_pairs() {
        get_all_pairs(tag<std::tuple<Ts...>>{},
                      std::make_index_sequence<sizeof...(Ts) * sizeof...(Ts)>{});
    }
}
void get_all() { detail::get_all_pairs<a, b, c>(); }