标签分派与部分特化类上的静态方法

51
假设我想编写一个通用函数void f<T>(),如果T是POD类型,则执行一件事,如果T是非POD类型(或任何其他任意谓词),则执行另一件事。
实现这个的一种方法是使用标签分发模式,就像标准库在迭代器类别中所使用的那样。
template <bool> struct podness {};
typedef podness<true> pod_tag;
typedef podness<false> non_pod_tag;

template <typename T> void f2(T, pod_tag) { /* POD */ }
template <typename T> void f2(T, non_pod_tag) { /* non-POD */ }

template <typename T>
void f(T x)
{
    // Dispatch to f2 based on tag.
    f2(x, podness<std::is_pod<T>::value>());
}

一种替代方法是使用部分特化类型的静态成员函数:
template <typename T, bool> struct f2;

template <typename T>
struct f2<T, true> { static void f(T) { /* POD */ } };

template <typename T>
struct f2<T, false> { static void f(T) { /* non-POD */ } };

template <typename T>
void f(T x)
{
    // Select the correct partially specialised type.
    f2<T, std::is_pod<T>::value>::f(x);
}

使用一种方法与另一种方法相比有什么优缺点?你会推荐哪种方法?


3
随你的便。我认为第二个版本更具有“类型化”的吸引力,因为它具有更少的辅助代码和更少的隐含概念。此外,我会为参数添加转发! - Kerrek SB
4个回答

17
我希望使用标签分发技术,因为:
  • 方便添加新标签
  • 易于继承(示例
  • 这是通用编程中相当常见的技术

在第二个示例中添加第三个变量似乎对我来说很棘手。例如,当您想要添加非POD-of-PODs类型时,您将不得不用其他内容替换template <typename T, bool> struct f2;中的bool(如果您喜欢,则为int),并将所有struct f2<T,bool-value>更换为struct f2<T,another-type-value>。所以对我来说,第二种变体看起来很难扩展。如果我说错了,请纠正我。


1
一种扩展它的方法是使用枚举模板参数,但我同意那会有些笨重。 - Peter Alexander

16

我喜欢一个对于简单的编译时分发来说,可读性更强的替代方案,它不需要使用[boost|std]::enable_if、标签或者部分特化。具体实现如下:

[请记住,布尔型可以转换为整数,零长度数组是无效的,并且有问题的模板会被丢弃(SFINAE)。此外,char (*)[n]是指向n个元素的数组的指针。]

template <typename T> 
void foo(T, char (*)[is_pod<T>::value] = 0)
{
    // POD
}

template <typename T> 
void foo(T, char (*)[!is_pod<T>::value] = 0)
{
    // Non POD
}

它的优点是不需要污染命名空间的外部类。现在,如果您想像在您的问题中那样将谓词外部化,可以执行以下操作:
template <bool what, typename T>
void foo(T, char (*)[what] = 0)
{
    // taken when what is true
}

template <bool what, typename T>
void foo(T, char (*)[!what] = 0)
{
    // taken when what is false
}

使用方法:

foo<std::is_pod<T>::value>(some_variable);

2
是的,这很聪明,但如果你不了解SFINAE,那么它会有一个严重的“WTF”因素,而其他方法则没有。尽管如此,我还是欣赏简洁明了。 - Peter Alexander
2
@Peter:SFINAE 已经有一个很大的 WTF 因素了。在使用 typename std::enable_if<whatever>::type 时,我认为会使 whatever 部分变得非常晦涩难懂。在这里,whatever 部分放在括号内是很清晰明了的。 - Alexandre C.
1
引用《侏罗纪公园》中的名言:“聪明的女孩”。为了使其更易读,你不应该选择使用“= NULL”,或者更好的是“= nullptr”吗? - kornman00
4
@AlexandreC。它们可能已经同样令人困惑,但是enable_if显然更加友好于搜索引擎。 - FrankHB
1
@FrankHB:我同意这个观点 - 最终取决于您的团队代码约定。然而,最好在确实需要时尽量避免使用SFINAE,正是因为它可能导致的“WTF”问题。 - Alexandre C.
显示剩余3条评论

12

实际上,两者都是标签分派模式。前者称为按实例分派标签,后者称为按类型分派标签

Boost.Geometry的主要作者Barend解释了这两种方法,并更喜欢后者。这在Boost.Geometry中被广泛使用。以下是总结的优点:

  • 不需要实例化标签,因为它唯一的目的是区分
  • 可以基于标签轻松定义新类型和常量
  • 接口中的参数可以反转,即distance(point, polygon);distance(polygon, point);只需要一个实现

2

我知道这是一个旧问题,答案已经被接受,但这可能是一个可行的替代方案:

template<typename T>
std::enable_if_t<std::is_pod<T>::value> f(T pod)
{
}

template<typename T>
std::enable_if_t<!std::is_pod<T>::value> f(T non_pod)
{
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接