在编译时收集模板参数

时间:2018-08-16 08:49:48

标签: c++ templates compile-time

我有一个使用其他许多类作为模板参数的类(如果需要的话,可以使用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
}

当前,这必须手动完成(对于不同的“收藏”类,必须在多个位置进行)。

是否可以将其更改为更好的可管理替代方式,例如:

所需情况:

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

最近几天,我一直在尝试通过提高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;
  }
};

3 个答案:

答案 0 :(得分:3)

规则注册

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

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]

答案 1 :(得分:0)

我不知道您可以更改多少代码,但是...如果您可以数字对类Class1Class2,... {{1 }}使它们专门化模板类ClassN

ClassX

以使// class_base.h (additional header) template <std::size_t> struct ClassX; 成为Class1ClassX<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专业化类别的数量-使用一个类帮助器classXstd::index_sequence(从C ++ 14开始可用)

std::make_index_sequence

现在,在specifictemplate.h中,您只需编写即可(如果我理解正确,并且所有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>> { }; 都相等的话)

specificClass

答案 2 :(得分:0)

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

这将使您能够编写:

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>类,该类从version<N - 1>继承(递归):

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

然后使用version定义一个函数重载列表,其中函数重载的数字__COUNTER__不断增加:

// 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是什么,所以我可能完全不在这里。