首页 > 解决方案 > Collect template parameters at compile time

问题描述

I have a class which takes many other classes as template arguments (for compile-time grammar generation with TAO PEGTL if it matters) and would like to know if there is a better and more extensible way to do this, than typing everything manually.

Current Situation:

//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
}

Currently this has to be done manually (and in multiple locations for different "collection" classes).

Is there a way to change this into a better manageable alternative like:

Desired Situation:

//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()

I was trying to achieve this with boost PP and MPL for the last days but I'm not sure if it is possible at all.

EDIT:

Specific instances are required by pegtl like this: There are actions which are predefined as:

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

And have to be instantiated as:

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

标签: c++templatescompile-time

解决方案


规则登记

我建议在代码库的一个位置显式手动注册所有“规则”(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. struct SuperRule{};在“super_rule.hpp”中定义
  2. 附加到“registered_rules.hpp”中SuperRule的列表。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应该将其第一个输入的可变参数类型参数“注入”到其第二个输入中:

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注册规则

在您的问题中,您询问如何为所有注册规则专门化模板类: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]

推荐阅读