可变参数模板作为模板参数:使用GCC可以进行推断,但是使用Clang则不行。

22

在使用GCC 4.7.2和Clang 3.1编译一些C++11代码时,我遇到了一个问题,Clang无法推断出模板参数,而GCC可以成功地推断出来。

更抽象地说,代码看起来像这样:

src/test.cc:

struct Element {
};

template <typename T>
struct FirstContainer {
};

template <typename T, typename U = Element>
struct SecondContainer {
};

template <template <typename> class Container>
void processOrdinary(Container<Element> /*elements*/) {
}

template <template <typename, typename> class Container>
void processOrdinary(Container<Element, Element> /*elements*/) {
}

template <template <typename, typename...> class Container>
void processVariadic(Container<Element> /*elements*/) {
}

int main() {
  // This function instantiation works in both GCC and Clang.
  processOrdinary(FirstContainer<Element>{});
  // This function instantiation works in both GCC and Clang.
  processOrdinary(SecondContainer<Element>{});
  // This function instantiation works in both GCC and Clang.
  processVariadic(FirstContainer<Element>{});
  // This function instantiation works in both GCC and Clang.
  processVariadic<SecondContainer>(SecondContainer<Element>{});
  // This function instantiation works in GCC but not in Clang.
  processVariadic(SecondContainer<Element>{});
  return 0;
}

通过阅读标准中§14.3.3的示例和§14.8.2的规范,我认为推断应该是可行的,但我不能确定。以下是构建时得到的输出:

mkdir -p build-gcc/
g++ -std=c++0x -W -Wall -Wextra -Weffc++ -pedantic -c -o build-gcc/test.o src/test.cc
g++  -o build-gcc/test build-gcc/test.o
mkdir -p build-clang/
clang++ -std=c++11 -Weverything -Wno-c++98-compat -c -o build-clang/test.o src/test.cc
src/test.cc:34:3: error: no matching function for call to 'processVariadic'
  processVariadic(SecondContainer<Element>{});
  ^~~~~~~~~~~~~~~
src/test.cc:21:6: note: candidate template ignored: failed template argument deduction
void processVariadic(Container<Element> /*elements*/) {
     ^
1 error generated.
make: *** [build-clang/test.o] Fel 1

结果为什么会不同?是GCC粗心、Clang愚蠢,还是我的代码包含了未指定的行为或者以上情况都有可能导致结果差异。


我同意你的观点。C++11最终草案中所见的一切都表明这应该是可行的。特别是14.3.3.3章节相关。 - Vaughn Cato
你的例子缺少了 typedef int Element;,对吗? - Quuxplusone
不,代码开头我定义了一个名为Element的结构体。 - psyill
1
你是否尝试将此问题发布到clang/llvm邮件列表中?他们可能有更深入的了解,也会很高兴知道他们的实现是否不完整。 - Alexander Oh
1个回答

7

Clang正在尝试推断此调用的参数:

processVariadic(SecondContainer<Element>{});

由于SecondContainer有一个默认的模板参数,所以这等同于:

processVariadic(SecondContainer<Element, Element>{});

因此,它使用 P = Container<Element>A = SecondContainer<Element, Element> 进行模板参数推导。它可以立即推断出 Container 模板参数是 SecondContainer
接下来,它考虑模板参数。由于参数类型已经完全解析,Clang 认为参数必须具有同样多的类型,否则推导将无法成功(它不考虑默认参数)。因此它标记了推导失败。
那么应该发生什么呢?用 [temp.deduct.type]p8 的话说,

如果 PA 具有以下形式之一,则可以推导模板类型参数 T、模板模板参数 TT 或模板非类型参数 i
[...]
TT<T>
TT<i>
TT<>
其中 [...] <T> 表示模板参数列表,至少有一个参数包含 T<i> 表示模板参数列表,至少有一个参数包含 i<> 表示模板参数列表,没有参数包含 Ti

为了匹配模板参数,我们转向[temp.deduct.type]p9

如果 P 的形式包含 <T><i>,则将相应模板参数列表 P 的每个参数 Pi 与相应的 A 模板参数列表的参数 Ai 进行比较。

这里有两件事需要注意。一是该规则没有说明如果列表 PiAi 长度不同会发生什么(在这种情况下它们是不同长度的),常见的解释似乎是不会检查不匹配的项。另一个是无论如何都不应遵循此规则,因为 P 的形式不包含 <T><i>(它只包含 <>,因为其中没有模板参数)。
因此,Clang 拒绝这段代码是错误的。我已经在 r169475 中修复了它。

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