在这个解决方案中,我倾向于 TMP 而不是宏。
第一步是收集所有声明的绑定。允许自由声明绑定并将它们分散到其他代码中可能是可行的。但是,它需要某种方式来保持和更新列表的状态,这几乎是您在 C++ 中想要做的最神秘的事情。所以我们不要那样做。
绑定文件必须只包含预处理器指令和对宏的调用BIND
,如下所示:
BIND(Cat, Dog)
BIND(Cat, Tree)
BIND(Cat, Rat)
BIND(Dog, Tree)
换句话说,预处理文件必须只包含来自BIND
. 然后我们可以将它们夹在bindTop.h
and之间bindBottom.h
:
template <class...>
struct pack;
// Stuff in bindTop.h
#define BIND(T, U) \
pack<struct T, struct U>,
using AllBindings = pack<
// End of bindTop.h, beginning of the binding file(s)
BIND(Cat, Dog)
BIND(Cat, Tree)
BIND(Cat, Rat)
BIND(Dog, Tree)
// End of the binding file(s), beginning of bindBottom.h
void // Pairs up with the last comma,
// will be silently ignored in further processing
>;
#undef BIND
// Stuff in bindBottom.h
现在我们在里面有了绑定列表AllBindings
。
下一步:我们如何将成员注入到类中?我已经放弃了宏,而是使用了成员继承。所以类定义如下:
struct Cat : WithBindings<Cat, AllBindings> { };
...最终将继承几个结构,这些结构将定义成员Right<Dog> dogs
,Right<Tree> trees
和Right<Rat> rats
, 因此几乎可以像访问它们一样访问它们。
但是如何声明Right<Dog>
必须调用类型的成员dogs
?当然是宏!让我们为左右制作基类制作空模板:
template <class T, class Binding>
struct MakeLeftMember { };
template <class T, class Binding>
struct MakeRightMember { };
然后我们将使用一个宏来为我们的每个类专门化这些,使用类的名称和相应成员的名称:
#define BINDING_MEMBER_NAME(type_, memberName_) \
template <class T> struct MakeLeftMember<T, pack<type_, T>> { \
Left<type_> memberName_; \
}; \
template <class T> struct MakeRightMember<T, pack<T, type_>> { \
Right<type_> memberName_; \
}
Binding
预计将是pack<L, R>
我们定义的其中之一BIND
。现在实例化 eg将仅在isMakeLeftMember<T, pack<L, R>>
时分派到特化,也就是说,绑定确实是 T 的左绑定。然后特化将生成适当命名的成员以由 继承。在其他情况下,选择了基本模板,没有任何反应。T
R
Left<L>
T
最后一个缺失的链接当然是WithBindings<T, AllBindings>
,它只是将所有绑定分派给成员制造商并继承生成的成员:
template <class T, class... Bindings>
struct WithBindings<T, pack<Bindings...>>
: MakeLeftMember <T, Bindings>...
, MakeRightMember<T, Bindings>... { };
我们开始了。在 Coliru 上现场观看!