在编译时收集模板参数

Collect template parameters at compile time

我有一个 class 需要许多其他 classes 作为模板参数(用于编译时语法生成 TAO PEGTL 如果重要的话)并且想知道是否有是比手动输入所有内容更好、更可扩展的方法。

现状:

//Class1.h
class Class1 {}
...
//ClassN.h
class ClassN {}

//Collection.h
struct collectionClass : templateClass<Class1,...,ClassN>

//SpecificTemplate.h
template<> struct specificClass<Class1>{
   //Do the same
}
...
template<> struct specificClass<ClassN>{
   //Do the same
}

目前这必须手动完成(并且在不同的 "collection" classes 的多个位置)。

有没有办法将其更改为更易于管理的替代方案,例如:

期望的情况:

//Class1.h
class Class1 {}
REGISTER_CLASS(Class1)
...
//ClassN.h
class ClassN {}
REGISTER_CLASS(ClassN)

//Collection.h
struct collectionClass : templateClass<REGISTERED_CLASSES>

//SpecificTemplate.h
CREATE_CLASSES_FROM_REGISTERED()

最近几天我一直在尝试使用 boost PP 和 MPL 来实现这一点,但我不确定是否可行。

编辑:

pegtl 需要这样的特定实例: 有预定义为的操作:

template<typename Rule>
struct Action : tao::pegtl::nothing<Rule> {
};

并且必须实例化为:

template<>
struct Action<specificRule> {
  static void apply0(State &state) {
    state.rule = specificRule::ID;
  }
};

规则注册

我建议在代码库的一个地方显式手动注册所有 "rules"(Class1,...,ClassN):

// foo_rule.hpp
#pragma once
struct FooRule {};

// bar_rule.hpp
#pragma once
struct BarRule {};

// foobar_rule.hpp
#pragma once
struct FoobarRule {};

// registered_rules.hpp
#pragma once
#include <tuple>
#include "foo_rule.hpp"
#include "bar_rule.hpp"
#include "foobar_rule.hpp"
using RegisteredRules = std::tuple<FooRule, BarRule, FoobarRule>;

上面的机制对于任何阅读代码的人来说都是显而易见的:我们可以绝对确定注册了哪些规则。

缺点显然是规则定义和规则注册的分离:添加一个名为 SuperRule 的新规则需要两个步骤:

  1. 在"super_rule.hpp"
  2. 中定义struct SuperRule{};
  3. SuperRule 添加到 "registered_rules.hpp" 中的 RegisteredRules 的列表中。

显然忘记第 2 步是有危险的。如果您愿意,您可以发明一种机制来防止这种错误,但让我们专注于您问题的其余部分。

从包装所有已注册规则的包装器继承

您正在寻求生成此代码的策略:

struct FirstCollection : TemplateClass<FooRule, BarRule/*, ...*/> {};
struct SecondCollection : TemplateClass<FooRule, BarRule/*, ...*/> {};
// where /*, ...*/ refers to all remaining rules which have been registered

让我们为此使用一个名为 rewrap 的原语。生成上述继承的代码为

struct FirstCollection : rewrap<RegisteredRules, TemplateClass> {};
struct SecondCollection : rewrap<RegisteredRules, TemplateClass> {};

显然,rewrap 应该 "inject" 将其第一个输入的可变类型参数转换为第二个输入:

template<class OldWrapped, template<class...> class NewWrapper>
using rewrap = /* to be implemented */

static_assert(
  std::is_same<
    rewrap<std::pair<int, double>, std::tuple>,
    std::tuple<int, double>
  >{}
);

static_assert(
  std::is_same<
    rewrap<std::tuple<char, long>, std::pair>,
    std::pair<char, long>
  >{}
);

专门Action注册规则

在您的问题中,您询问如何专门化模板化class Action所有注册规则:

template<>
struct Action<FooRule>{
  static void apply0(State& state) {
    // do the same
  }
}
/*...*/
template<>
struct Action<FoobarRule>{
  static void apply0(State& state) {
    // do the same
  }
}

相反,我建议使用部分专业化。让我们假设您被允许将第二个模板参数添加到 Action:

template<class Rule>
struct Nothing {
  static void apply0(State&) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
};

template<
  class Rule,
  class Enable = void
> struct Action
  : Nothing<Rule>
{};

第二个模板参数可以用来玩常见的SFINAE游戏:

template<
  class SpecificRule
> struct Action<
  SpecificRule,
  std::enable_if_t<
    is_wrapped_in<SpecificRule, RegisteredRules>// to be implemented
  >
> {
  static void apply0(State&) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
};

您显然需要的是另一个名为 is_wrapped_in 的原语。

C++17 中的完整示例

#include <iostream>
#include <optional>
#include <tuple>
#include <type_traits>
#include <utility>

////////////////////////////////////////////////////////////////////////////////
// rewrap

namespace detail {

template<
  class OldWrapped,
  template<class...> class NewWrapper
> struct Rewrap;

template<
  template<class...> class OldWrapper,
  class... Wrappees,
  template<class...> class NewWrapper
> struct Rewrap<
  OldWrapper<Wrappees...>,
  NewWrapper
> {
  using T = NewWrapper<Wrappees...>;
};

}// detail

template<class OldWrapped, template<class...> class NewWrapper>
using rewrap = typename detail::Rewrap<OldWrapped, NewWrapper>::T;

static_assert(
  std::is_same<
    rewrap<std::pair<int, double>, std::tuple>,
    std::tuple<int, double>
  >{}
);

static_assert(
  std::is_same<
    rewrap<std::tuple<char, long>, std::pair>,
    std::pair<char, long>
  >{}
);

////////////////////////////////////////////////////////////////////////////////
// is_wrapped_in

namespace detail {

template<class T, class Wrapped>
struct IsWrappedIn;

template<class T, template<class...> class Wrapper, class... Wrappees>
struct IsWrappedIn<T, Wrapper<Wrappees...>>
  : std::bool_constant<(... || std::is_same<T, Wrappees>{})>
{};

}// detail

template<class T, class Wrapped>
constexpr bool is_wrapped_in = detail::IsWrappedIn<T, Wrapped>::value;

static_assert(is_wrapped_in<int, std::tuple<char, char, int, long>> == true);
static_assert(is_wrapped_in<int, std::tuple<char, char, long, long>> == false);
static_assert(is_wrapped_in<int, std::pair<int, int>> == true);

////////////////////////////////////////////////////////////////////////////////
// registered_rules

struct UnregisteredRule {};

struct FooRule {};
struct BarRule {};
struct FoobarRule {};

using RegisteredRules = std::tuple<FooRule, BarRule, FoobarRule>;

////////////////////////////////////////////////////////////////////////////////
// collections

template<class... Rules>
struct TemplateClass {
  using Root = TemplateClass<Rules...>;// convenience alias for derived classes
};

struct FirstCollection : rewrap<RegisteredRules, TemplateClass> {};
struct SecondCollection : rewrap<RegisteredRules, TemplateClass> {};

static_assert(
  std::is_same<
    FirstCollection::Root,
    TemplateClass<FooRule, BarRule, FoobarRule>
  >{}
);

static_assert(
  std::is_same<
    SecondCollection::Root,
    TemplateClass<FooRule, BarRule, FoobarRule>
  >{}
);

////////////////////////////////////////////////////////////////////////////////
// action

struct State {};

template<class Rule>
struct Nothing {
  static void apply0(State&) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
};

template<
  class Rule,
  class Enable = void
> struct Action
  : Nothing<Rule>
{};

template<
  class SpecificRule
> struct Action<
  SpecificRule,
  std::enable_if_t<
    is_wrapped_in<SpecificRule, RegisteredRules>
  >
> {
  static void apply0(State&) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
};

////////////////////////////////////////////////////////////////////////////////

int main() {
  State state{};

  Action<UnregisteredRule>::apply0(state);

  Action<FooRule>::apply0(state);
  Action<BarRule>::apply0(state);
  Action<FoobarRule>::apply0(state);
}

使用 GCC 8.2.0 的输出:

static void Nothing<Rule>::apply0(State&) [with Rule = UnregisteredRule]
static void Action<SpecificRule, typename std::enable_if<is_wrapped_in<SpecificRule, std::tuple<FooRule, BarRule, FoobarRule> >, void>::type>::apply0(State&) [with SpecificRule = FooRule]
static void Action<SpecificRule, typename std::enable_if<is_wrapped_in<SpecificRule, std::tuple<FooRule, BarRule, FoobarRule> >, void>::type>::apply0(State&) [with SpecificRule = BarRule]
static void Action<SpecificRule, typename std::enable_if<is_wrapped_in<SpecificRule, std::tuple<FooRule, BarRule, FoobarRule> >, void>::type>::apply0(State&) [with SpecificRule = FoobarRule]

我不知道您可以更改多少代码,但是...如果您可以 numerate classes Class1Class2, ... ClassN 使他们专业化模板 class ClassX

// class_base.h (additional header)
template <std::size_t>
struct ClassX;

Class1变成ClassX<1u>Class2变成ClassX<2u>,等等

// class1.h
template <>
struct ClassX<1u>
 { };

// class2.h
template <>
struct ClassX<2u>
 { };

// a file for ClassX<3>, one for ClassX<4>, etc

你可以让你的 collectionclass 可以继承所有 classX 专业 - 如果它们的编号是连续的并且如果你注册 classX 专业的编号 classes -- 使用 class 助手,std::index_sequencestd::make_index_sequence(从 C++14 开始可用)

static constexpr std::size_t numOfClasses { 2u }; // num of ClassX classes

template <typename...>
struct templateClass
 { };

template <typename>
struct cc_helper;

template <std::size_t ... Is>
struct cc_helper<std::index_sequence<Is...>>
   : public templateClass<ClassX<Is+1u>...> // +1u if start from `ClassX<1u>
 { };                                       // without +1u is start from
                                            // ClassX<0u>

struct collectionclass
   : public cc_helper<std::make_index_sequence<numOfClasses>>
 { };

现在,在specifictemplate.h中,你可以简单地写(如果我理解正确并且所有specificClass基本相等)

// specifictemplate.h

template <typename>
struct specificClass;

template <std::size_t I>
struct specificClass<ClassX<I>>
 {
   //Do the same only one time
 };

在执行上述重载时,您可以结合使用函数重载的巧妙技巧以及派生类型比其基类型具有更高优先级的事实。

这将使您能够编写:

TYPE_COLLECTOR_START(foo);

TYPE_COLLECTOR_ADD(foo, int);
TYPE_COLLECTOR_ADD(foo, std::pair<bool, bool>);
TYPE_COLLECTOR_ADD(foo, char);

TYPE_COLLECTOR_END(foo);

static_assert(
    std::is_same_v<foo, type_collector::list<int, std::pair<bool, bool>, char>>
);

这个技巧不符合标准,因为它使用 __COUNTER__ 编译器扩展宏,但几乎所有编译器都定义了它。

基本上,您定义一个 version<N> class,它继承自 version<N - 1>(递归):

template <unsigned long N> struct version : version<N - 1> {};
template <> struct version<0> {};

然后使用 __COUNTER__:

定义一个函数重载列表,其中 version 个数递增
// the result is stored in this type
template <class... Ts>
struct list {
    template <class T>
    using add = list<Ts..., T>;
};

// macro TYPE_COLLECTOR_START usage will expand to
auto get(version<0>) -> list<>;

// macro TYPE_COLLECTOR_ADD usages will expand to
auto get(version<1>) -> decltype(get(version<0>{}))::add<int>;
auto get(version<2>) -> decltype(get(version<1>{}))::add<std::pair<bool, bool>>;
auto get(version<3>) -> decltype(get(version<2>{}))::add<char>;

// macro TYPE_COLLECTOR_END usage will expand to
using foo = decltype(get(version<3>{}));

显然,您希望避免名称冲突(并且可能同时将类型收集到两个不同的列表中;交错),因此我编写了一个更详尽的示例以供参考。

编辑

对于您的特定用例,如果我理解正确,您可以在我上面提供的内容上定义包装器宏,将类型添加到列表并定义专业化:

#define REGISTER_RULE(rule)\
    TYPE_COLLECTOR_ADD(registered_rules, rule)\
    \
    template <>\
    struct Action<rule_name> {\
        static void apply0(State& state) {\
            state.rule = rule_name::ID;\
        }\
    }

然后你会得到类似的东西:

// registered_rules.h
namespace tao_pegtl_namespace { TYPE_COLLECTOR_START(registered_rules); }

// rule1.h
#include "registered_rules.h"
namespace your_namespace { class rule1 {}; }
namespace tao_pegtl_namespace { REGISTER_RULE(your_namespace::rule1); }

// rule2.h
#include "registered_rules.h"
namespace your_namespace { class rule2 {}; }
namespace tao_pegtl_namespace { REGISTER_RULE(your_namespace::rule2); }

// rules_collection.h
namespace tao_pegtl_namespace {
    TYPE_COLLECTOR_END(registered_rules);
    struct rules_collection : templateClass<(unpack `registered_rules`...)> {}
}

这不允许您拥有包含所有动作专业化的不同文件(SpecificTemplate.h)。我不知道 tao::pegtl 是什么,所以我可能完全离开这里。