6

我在以下情况下遇到问题:

#include <iostream>
#include <type_traits>

#define TRACE void operator()() const { std::cerr << "@" << __LINE__ << std::endl; }

template <class T>
struct check : std::true_type {};

template <class F, class T, class Check=void>
struct convert {
  TRACE;// first case
};

template <class F, class T>
struct convert<F*, T, typename std::enable_if<(check<F>::value && check<T>::value), void>::type> {
  TRACE; // second case
};

template <class T>
struct convert<int*, T, typename std::enable_if<(check<T>::value), void>::type> {
  TRACE; // third case
};

然后

convert<int*, int> c;
c();

将报告 g++-4.5、g++-4.6、g++-4.7 和 clang++-3.1 中的模棱两可的类模板实例化(全部带有选项 -std=c++0x)

但是,如果我将第三种情况的支票替换为

typename std::enable_if<(check<int>::value && check<T>::value), void>:type

然后 clang++-3.1 工作正常。

是编译器错误还是标准?

4

2 回答 2

1

在这个问题中出现了类似的问题

因为第二个和第三个部分特化都与 匹配convert<int*, int>,所以编译器将构建两个测试函数模板,其中两个部分特化的类模板作为参数提供:

template <class F, class T> 
void fun2(convert<F*, T, typename std::enable_if<
    (check<F>::value && check<T>::value), void>::type>
);

template <class T> 
void fun3(convert<int*, T, typename std::enable_if<
    (check<T>::value), void>::type>
);

然后编译器通过将一个函数的一组转换参数交叉替换到另一个函数模板来检查一个函数模板是否比另一个函数模板更专业,并检查是否可以推导出所有模板参数。如果这两种方式都有效,那么任何一个功能都不会比另一个更专业,并且会产生歧义。

这里的问题是,这std::enable_if< (check<F>::value && check<T>::value), void>::type>是一个非演绎的上下文,在这个论证演绎游戏中不会被评估。::编译器仅检查通用表达式是否具有相同的结构形式(推导定界符前面的任何内容),而不检查它们是否具有相同的值(true_type在这种情况下)。

只有在第三个部分特化中增加额外的部分,第三个特check<int>::value化才会比第二个更特化。另一个“修复”是手动放入true_type参数Check,但是,编译器在参数推导期间不会为您执行此操作。

更新:作为对 Johannes Schaub - litb 的回应:你是对的,std::check<int>放入的代码std::enable_if无法在 Ideone 和 MSVC++ 2010 上编译。这是怎么回事?根据14.8.2.4 第 11 条 [temp.deduct.partial]

在大多数情况下,所有模板参数都必须具有值才能成功进行推导,但出于偏序目的,模板参数可能会保持没有值,前提是它未用于偏序的类型。[ 注意:在非推导上下文中使用的模板参数被视为已使用。— 尾注] [示例:

template <class T> T f(int); // #1
template <class T, class U> T f(U); // #2
void g() {
    f<int>(1); // calls #1
}

对于 OP 的代码,我将其解释为未使用的参数将是std::enable_if表达式。我的猜测是 Clang 3.1 做了一些 Ideone 和 MSVC++ 不做的表达式匹配。我不明白在上述引用的上下文中,这是否是标准要求的:应该只忽略未使用的模板参数,还是忽略未使用的模板表达式?标准中的其他部分出现了诸如“不要求实现使用英勇的努力”之类的短语。也许 Clang 在这方面比 MSVC++ 或 Ideone 更英勇。

于 2012-07-16T09:04:18.920 回答
0

你有

template <class F, class T>
struct convert<F*, T, typename std::enable_if<(check<F>::value && check<T>::value), void>::type> {
  TRACE; // second case
};

template <class T>
struct convert<int*, T, typename std::enable_if<(check<T>::value), void>::type> {
  TRACE; // third case
};

当您使用convert<int*, int> c;时,编译器无法选择他需要使用的结构,因为它们都适合。

请注意,您check<F>::value在第一个模板中使用。这意味着即使您通过了例如 an int *,您也将拥有check<int>::value,而不是check<int *>::value

于 2012-07-16T07:44:40.263 回答