编程模板的风格

Style of programming templates

本文关键字:风格 编程      更新时间:2023-10-16

如果可以选择,你会选择哪一个?

template<class CheckEveryNode<true>>
struct X;
or 
template<bool CheckEveryNode>
struct X;

从设计人员的角度来看,这并不明显,一方面我们在实际代码中具有可读性:

//taking first approach
//somewhere in the code
X<CheckEveryNode<false>> x;

在另一边有更多的东西要输入,有人更喜欢:

//taking second approach
//somewhere in the code
X<false> x;//but here we don't really see immediately what the false means.  

所以,期待你的意见/建议

经常涉水元编程,我只能推荐一种"冗长"的方法,但我不太喜欢您提出的第一种方法。

理想情况下,当使用Policy时,您不只是传递一个标志,而是传递一个Policy对象,它允许用户随意自定义它,而不是依赖于您自己的预定义值。

例如:

struct NodeCheckerTag {};
struct CheckEveryNode {
  typedef NodeCheckerTag PolicyTag;
  void check(List const& list);
};
struct CheckFirstNode {
  typedef NodeCheckerTag PolicyTag;
  void check(List const& list);
};
template <typename Rand>
struct CheckSomeNodes {
  typedef NodeCheckerTag PolicyTag;
  CheckSomeNodes(Rand rand): _rand(rand) {}
  void check(List const& list);
  Rand _rand;
};

你的类应该允许用户选择选择哪个策略:

template <typename NodeChecker>
class X: NodeChecker // allow stateful implementation but let EBO kick in
{
};

PolicyTag用于多个策略的存在:

template <typename NodeChecker, typename NodeAllocator, typename NodeNotifier>
class X;

你通常应该提供合理的默认值,但总是有的情况下,我想自定义只是最后一个!,通过切换到可变的模板,你可以得到:

template <typename Tag, typename Default, typename... Policies>
struct PolicySelector
{
  typedef /**/ type;
};
template <typename... Policies>
class X: Policies...
{
  typedef typename PolicySelector<NodeCheckerTag, CheckNoNode,
    Policies...>::type NodeCheckerPolicy;
  typedef typename PolicySelector<NodeAllocatorTag, StdAllocator,
    Policies...>::type NodeAllocatorPolicy;
  ...
};

注意,通过从策略继承,如果您只关心调用某些函数,那么选择可能是不必要的。只有在需要隐藏在策略中的内部类型时才需要这样做,因为这些类型应该在派生类(这里的X)中显式地定义。

First

template<class CheckEveryNode<true>>
struct X;

是错的,应该是

template<template<bool> CheckEveryNode>
struct X{};

将需要您专门化true或false条件:

// above is true case, below is false case
template<>
struct X<CheckEveryNode<false> >{};

或者以以下方式使用部分专门化:

template<class CheckEveryNode>
struct X;
template<bool B>
struct X<CheckEveryNode<B> >{
  // real implementation
};

除此之外,第二个更容易实现,也更容易阅读,因为它是"预期的"。当您提供模板参数时,您知道它的用途,不需要额外的结构体。

如果您关心的是当您有多个策略时发生的情况,并且您不知道各种布尔值的含义,则可以将布尔值全部消除并使用标记。

struct CheckEvery { };
struct CheckNone { };
template<typename Check> struct Thingy;
template<> Thingy<CheckEvery> { ... };
template<> Thingy<CheckNone> { ... };

或者你可以让策略来决定要做的工作,而不是专门化。

struct CheckEvery { bool shouldCheck(int) { return true; } };
struct CheckNone { bool shouldCheck(int) { return false; } };
struct CheckOdds { bool shouldCheck(int i) { return i % 2; } };
template<typename Checker> struct Thingy {
  Checker checker;
  void someFunction(int i) { if(checker.shouldCheck(i)) check(i); }
};

这个例子没有很好地具体化,但它给了你一个想法。例如,您可能希望向Thingy构造函数提供Checker对象。

我也喜欢第二个。我的哲学是,如果你不需要,为什么要创建一个额外的类型?

第一种方法看起来很麻烦。我会选择第二种,因为第一种没有令人信服的理由。至于可读性,第二个是可读的。事实上,第一种格式由于其语法的大杂烩而降低了可读性。