【问题标题】:Is this "Tag Dispatching"?这是“标签调度”吗?
【发布时间】:2014-07-21 12:34:22
【问题描述】:

假设我有一些代码:

void barA() { }
void barB() { }

void fooA() {
  // Duplicate code...
  barA();
  // More duplicate code...
}

void fooB() {
  // Duplicate code...
  barB();
  // More duplicate code...
}

int main() {
  fooA();
  fooB();
}

我想删除 fooAfooB 之间的重复代码我可以这样做:

struct A { };
struct B { };

template<typename Tag> void bar();
template<> void bar<A>() { }
template<> void bar<B>() { }

template<typename Tag> void foo() {
  // Duplicate code
  bar<Tag>();
  // More duplicate code
}

int main() {
  foo<A>();
  foo<B>();
}

我引入了两个空的“标签”类来指示要使用哪个bar,并根据标签类模板化foobar。这似乎可以解决问题。问题:

  1. 这种技术有名字吗?这是“标签调度”的一个例子吗?从我读到的关于Tag dispatching 的内容来看,它略有不同,涉及带有标签参数的函数重载。可能来自 trait 类中的 typedef 的标记。
  2. 是否有更惯用的编译时技术来实现相同的目标?

编辑: 另一种可能性是使用bar 的函数重载而不是模板特化并将标签类作为参数传递:

struct A { };
struct B { };

void bar(A) { }
void bar(B) { }

template<typename Tag> void foo() {
  // Duplicate code
  bar(Tag());
  // More duplicate code
}

int main() {
  foo<A>();
  foo<B>();
}

【问题讨论】:

  • 我想称之为“标签调度”。

标签: c++ templates c++11 template-meta-programming


【解决方案1】:

这不是标签调度。正如您在问题中正确所说的那样,如果您使用 AB 的一些编译时特征来区分两者,然后使用它在两个不同的重载之间进行选择。

标签调度的一个很好的例子是std::advance 通常是如何实现的。该函数的签名是

template< class InputIt, class Distance >
void advance( InputIt& it, Distance n );

it如果满足RandomAccessIterator的要求,可以在一次操作中提升n的位置。对于较小的迭代器,我们必须在循环中推进it。所以一个实现可能会做类似于以下的事情:

namespace detail
{
  template<class InputIt, class Distance>
  void advance(InputIt& it, Distance n, std::random_access_iterator_tag) 
  {
    it += n;
  }

  template<class InputIt, class Distance>
  void advance(InputIt& it, Distance n, std::bidirectional_iterator_tag) 
  {
    if(n < 0) {
      while(n++) --it;
    } else {
      while(n--) ++it;
    }
  }

  template<class InputIt, class Distance>
  void advance(InputIt& it, Distance n, std::input_iterator_tag) 
  {
    assert(n >= 0);
    while(n--) ++it;
  }
}

template< class InputIt, class Distance >
void advance( InputIt& it, Distance n )
{
  detail::advance(it, n, 
                  typename std::iterator_traits<InputIt>::iterator_category());
}

我不知道你在做什么的具体名称。这只是一个如何遵循DRY 原则的示例。

如果barAB 的实例作为参数,那么我会以不同的方式实现它。我不会让bar 成为函数模板,然后提供特化,而是让重载解析为我完成这项工作。

void bar(A const&) { ... }
void bar(B const&) { ... }

但由于情况并非如此,因此提供明确的专业化似乎是做到这一点的正确方法。

【讨论】:

  • 尝试 CRTP 来修复 OP 的问题。基调用free函数中的dispatch方法,CRTP impl调用self()-&gt;foo(...),要么调用dispatch到free,要么(如果子覆盖)class local?
  • @Praetorian 在std:advance 示例中,看起来每次都构造和复制一个标记类,一个没有使用的类。你可以依靠编译器来优化它吗?
  • @Praetorian。我可以将空的AB 类的实例传递给bar 而不使用它。我在我的问题中添加了一个编辑来展示如何。但这会更好吗?
  • @user3502661 关于标签结构被优化掉 - 可以保证吗?不,但它是一个空结构,并且参数未命名,因此极有可能是。您当然可以传递AB 的空实例来进行标签调度,但IMO 感觉有点人为。这是否是一个好主意还取决于这两种类型的构造有多简单。最后,正如 Yakk 在 cmets 中提到的,您可以使用 CRTP 作为您的问题的解决方案。 Here's 一个很好的例子。维基百科也有一个很好的例子。
猜你喜欢
  • 2011-10-31
  • 2013-05-04
  • 1970-01-01
  • 1970-01-01
  • 2011-10-25
  • 2015-06-04
  • 1970-01-01
  • 1970-01-01
  • 2013-04-30
相关资源
最近更新 更多