8

template在重写函数时,我有时会使用一种技术,如下所示:

#include <utility>
template<int> struct unique_enum { enum class type {}; };
template<int index> using UniqueEnum = typename unique_enum<index>::type;
template<bool b, int index=1>
using EnableFuncIf = typename std::enable_if< b, UniqueEnum<index> >::type;
template<bool b, int index=1>
using DisableFuncIf = EnableFuncIf<!b, -index>;

// boring traits class:
template<typename T>
struct is_int : std::false_type {};
template<>
struct is_int<int> : std::true_type {};

#include <iostream>
// use empty variardic packs to give these two SFINAE functions different signatures:
template<typename C, EnableFuncIf< is_int<C>::value >...>
void do_stuff() {
  std::cout << "int!\n";
}
template<typename C, DisableFuncIf< is_int<C>::value >...>
void do_stuff() {
  std::cout << "not int!\n";
}

int main() {
  do_stuff<int>();
  do_stuff<double>();
}

这区别于do_stuffdo_stuff因为一个取 0 或多个UniqueEnum<1>s,而另一个取 0 或多个UniqueEnum<-1>s。gcc 4.8 认为这些不同的空包是不同的。

但是,在我尝试过的最新版本的 clang 中,这失败了:它将 0 s 的函数视为与 0 UniqueEnum<1>s 的函数相同UniqueEnum<-1>

在clang中有一些简单的解决方法,但我想知道我的上述技术是否合法——做两个函数template,它们仅通过空的可变参数包不同,实际上不同吗?

4

1 回答 1

2

我认为 GCC 是正确的,你的技术是正确的。基本上,由于C明确指定了类型参数,问题是:

一个。C函数模板签名中其他任何地方的替换首先发生,然后执行类型推导(这将导致替换失败);或者

湾。首先进行类型推导,然后进行替换(不会导致替换失败,因为对应的参数包为空,因此不会进行替换)。

似乎 GCC 假设 (1),而 Clang 假设 (2)。C++11 标准的第 14.8.2/2 段规定:

当指定显式模板参数列表时,模板参数必须与模板参数列表兼容,并且必须产生如下所述的有效函数类型;否则类型扣除失败。具体来说,在针对给定函数模板评估显式指定的模板参数列表时执行以下步骤:

— 指定的模板参数必须与类型的模板参数匹配(即类型、非类型、模板)。参数不能多于参数,除非至少一个参数是模板参数包,并且每个非包参数都应有一个参数。否则,类型推导失败。

— 非类型实参必须与对应的非类型模板形参的类型相匹配,或者必须可以转换为 14.3.2 中规定的对应的非类型形参的类型,否则类型推导失败。

指定的模板参数值替换为下面指定的相应模板参数

下一段接着说:

执行此替换后,将执行 8.3.5 中描述的函数参数类型调整。[...]

此外,第 14.8.2/5 段规定:

生成的替换和调整的函数类型用作模板参数推导的函数模板的类型。[...]

最后,第 14.8.2/6 段如下:

在模板参数推导过程中的某些点,有必要采用使用模板参数的函数类型并将这些模板参数替换为相应的模板参数。当任何显式指定的模板参数被替换为函数类型时,这在模板参数推导的开头完成,并且在模板参数推导结束时,当任何从默认参数推导或获得的模板参数被替换时再次完成。

这一切似乎都意味着首先执行替换,然后执行模板参数推导。因此,在任何一种情况下都应该发生替换失败,并且应该从重载集中丢弃两个模板之一。

不幸的是,似乎没有一个明确的规范来说明当模板参数被推导而不是被明确指定时应该是什么行为。

于 2013-06-14T22:43:03.530 回答