【问题标题】:Collect template parameters at compile time在编译时收集模板参数
【发布时间】:2018-08-16 08:49:48
【问题描述】:

我有一个类,它将许多其他类作为模板参数(用于编译时语法生成与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()

最近几天我试图通过 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;
  }
};

【问题讨论】:

  • 你能详细说明为什么你需要这样做而不是仅仅指定template&lt;class T&gt; struct specificClass&lt;T&gt; { /*Do the same*/ };吗?如果所有这些实例都是相同的,那么我看不到将每个实例拼写出来的目的。如果它们不同,CREATE_CLASSES_FROM_REGISTERED 怎么知道要改变什么?
  • 因为 /*Do the same*/ 会对 T 本身做一些事情。就像调用 T.method() 一样。所有类都相同,但必须针对语法框架专门实例化。
  • 你可以直接使用T.method()(如果所有类都有method())...也许你可以让这个例子更具体?
  • 那么您需要的是显式实例化吗? specificClassClassX 中的一些模板化的东西?
  • 已编辑问题,包含更多具体信息

标签: c++ templates compile-time


【解决方案1】:

规则注册

我建议在代码库的一个位置显式手动注册所有“规则”(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]

【讨论】:

  • 这看起来很有希望。我将在今天晚些时候使用这个示例。已经感谢您的尝试。
  • @NSchaef:更新了解释。
【解决方案2】:

我不知道您可以更改多少代码,但是...如果您可以 numerateClass1Class2、...ClassN 使其专业化一个模板类ClassX

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

这样Class1 变成ClassX&lt;1u&gt;Class2 变成ClassX&lt;2u&gt;,等等

// 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 特化类的数量——使用类帮助器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
 };

【讨论】:

  • 据我了解,问题是关于自动生成现有模板的模板特化。 “从索引基类派生”方法很有趣,但不是我觉得问题的核心(尽管标题暗示了 - 如果我对这一切的目标有更清晰的了解,我早就编辑过了)。
  • @MaxLanghof - 据我所知......我真的很困惑 OP 到底想要什么。我知道他(她?)想要一种自动生成collectionClass 的方法,该collectionClass 继承自一个依赖于注册类列表和specificClass 专业化列表的类。但我承认我几乎可以肯定这一点。等待OP正宗解读。
【解决方案3】:

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

这将使您能够编写:

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&lt;N&gt; 类,它继承自version&lt;N - 1&gt;(递归):

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

【讨论】:

    猜你喜欢
    • 2013-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-12
    • 2023-03-04
    • 2015-09-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多