std::enable_if的不明确的部分专门化

ambiguous partial specializations with std::enable_if

本文关键字:专门化 不明确 if enable std      更新时间:2023-10-16

我在如下条件下遇到了一个问题:

#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

然后叮当++-3.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参数中,但是,编译器在参数推导过程中不会为您这样做。

UPDATE:响应Johannes Schaub-litb:您是对的,将std::check<int>放入std::enable_if的代码不会在Ideone和MSVC++2010上编译。该怎么办?根据14.8.2.4第11条【临时扣除部分】

在大多数情况下,所有模板参数都必须具有值,以便推导成功,但出于偏序目的,模板如果参数未在用于部分排序的类型。[注:模板参数在非推导上下文中使用被认为是使用的。——尾注][示例:

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更英勇。

您有

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。这意味着,即使您传递了int *,您也将获得check<int>::value,而不是check<int *>::value