我遇到SFINAE问题:
在下面的代码中,我希望C ++编译器选择专门的仿函数并打印“special”,但它打印的是“general”。
#include <iostream>
#include <vector>
template<class T, class V = void>
struct Functor {
void operator()() const {
std::cerr << "general" << std::endl;
}
};
template<class T>
struct Functor<T, typename T::Vec> {
void operator()() const {
std::cerr << "special" << std::endl;
}
};
struct Foo {
typedef std::vector<int> Vec;
};
int main() {
Functor<Foo> ac;
ac();
}
如何修复它以便自动使用专用结构?注意我不想直接对Functor
上的Foo
结构进行专门化,但我希望将其专门用于Vec
类型的所有类型。
P.S。:我正在使用g ++ 4.4.4
答案 0 :(得分:11)
很抱歉在最后一个回答中误导了你,我想了一会儿它会更简单。所以我会尝试在这里提供一个完整的解决方案。解决此类问题的一般方法是编写 traits 帮助程序模板,并将其与enable_if
(C ++ 11,boost或手动实现)一起使用来决定类的特化:
<强>性状强>
一种简单的方法,不一定是最好的,但写得很简单:
template <typename T>
struct has_nested_Vec {
typedef char yes;
typedef char (&no)[2];
template <typename U>
static yes test( typename U::Vec* p );
template <typename U>
static no test( ... );
static const bool value = sizeof( test<T>(0) ) == sizeof(yes);
};
方法很简单,提供两个模板函数,返回不同大小的类型。其中一个采用嵌套的Vec
类型,另一个采用省略号。对于具有嵌套Vec
的所有类型,第一个重载是更好的匹配(省略号是任何类型的最差匹配)。对于那些没有嵌套Vec
的类型,SFINAE将丢弃该重载,剩下的唯一选项将是省略号。所以现在我们有一个特性来询问是否有任何类型具有嵌套的Vec
类型。
启用
您可以在任何库中使用它,或者您可以自己动手,这很简单:
template <bool state, typename T = void>
struct enable_if {};
template <typename T>
struct enable_if<true,T> {
typedef T type;
};
当第一个参数为false
时,基本模板是唯一的选项,并且没有嵌套的type
,如果条件为true
,则{{1}我有一个嵌套的enable_if
,可以与SFINAE一起使用。
<强>实施强>
现在我们需要提供仅使用嵌套type
的那些类型的SFINAE模板和专门化:
Vec
每当我们使用类型实例化template<class T, class V = void>
struct Functor {
void operator()() const {
std::cerr << "general" << std::endl;
}
};
template<class T>
struct Functor<T, typename enable_if<has_nested_Vec<T>::value>::type > {
void operator()() const {
std::cerr << "special" << std::endl;
}
};
时,编译器将尝试使用特化,然后将实例化Functor
并获取传递给has_nested_Vec
的真值。对于值为enable_if
的那些类型,false
没有嵌套的enable_if
类型,因此特殊化将在SFINAE中被丢弃,并且将使用基本模板。
您的具体案例
在您的特定情况下,您似乎并不需要专门设置整个类型而只需要操作符,您可以将这三个元素混合为一个元素:type
调度到其中一个元素基于Functor
的存在的两个内部模板化函数,无需Vec
和特征类:
enable_if
答案 1 :(得分:4)
即使这是一个老问题,我认为仍然值得为快速修复原始代码提供更多选择。
基本上,问题不在于使用SFINAE(实际上该部分很好),而是将主模板(void
)中的默认参数与部分专业化中提供的参数匹配(typename T::Vec
)。由于主模板中的默认参数,Functor<Foo>
实际上意味着Functor<Foo, void>
。当编译器尝试使用特化实例化它时,它会尝试将两个参数与特化中的参数匹配并失败,因为void
不能替换std::vector<int>
。然后它回退到使用主模板进行实例化。
因此,假设所有Vec
为std::vector<int>
s的最快修复方法是替换该行
template<class T, class V = void>
用这个
template<class T, class E = std::vector<int>>
现在将使用专门化,因为参数将匹配。简单,但限制太多。显然,我们需要更好地控制特化中参数的类型,以使其匹配我们可以指定为主模板中的默认参数的东西。一个不需要定义新特征的快速解决方案是:
#include <iostream>
#include <vector>
#include <type_traits>
template<class T, class E = std::true_type>
struct Functor {
void operator()() const {
std::cerr << "general" << std::endl;
}
};
template<class T>
struct Functor<T, typename std::is_reference<typename T::Vec&>::type> {
void operator()() const {
std::cerr << "special" << std::endl;
}
};
struct Foo {
typedef std::vector<int> Vec;
};
int main() {
Functor<Foo> ac;
ac();
}
这适用于任何有意义的Vec
类型,例如基本类型和数组,以及它们的引用或指针。
答案 2 :(得分:3)
检测成员类型存在的另一种方法是使用void_t
。由于有效的部分特化优于一般实现,只要它们与默认参数匹配,我们需要一个在有效时评估为void
的类型,并且仅在指定成员存在时有效;这种类型通常(并且,从C ++ 17开始,规范地称为void_t
。
template<class...>
using void_t = void;
如果您的编译器没有正确支持它(在早期的C ++ 14编译器中,别名模板中未使用的参数不能保证确保SFINAE,打破上述void_t
),可以使用变通方法。< / p>
template<typename... Ts> struct make_void { typedef void type; };
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
从C ++ 17开始,void_t
中的实用程序库中提供了type_traits
。
#include <iostream>
#include <vector>
#include <type_traits> // For void_t.
template<class T, class V = void>
struct Functor {
void operator()() const {
std::cerr << "general" << std::endl;
}
};
// Use void_t here.
template<class T>
struct Functor<T, std::void_t<typename T::Vec>> {
void operator()() const {
std::cerr << "special" << std::endl;
}
};
struct Foo {
typedef std::vector<int> Vec;
};
int main() {
Functor<Foo> ac;
ac();
}
这样,输出为special
,如预期的那样。
在这种情况下,由于我们正在检查是否存在成员类型,因此该过程非常简单;它可以在没有表达SFINAE或type_traits
库的情况下完成,允许我们在必要时重写检查以使用C ++ 03工具。
// void_t:
// Place above Functor's definition.
template<typename T> struct void_t { typedef void type; };
// ...
template<class T>
struct Functor<T, typename void_t<typename T::Vec>::type> {
void operator()() const {
std::cerr << "special" << std::endl;
}
};
据我所知,这应该适用于大多数(如果不是全部)支持SFINAE的C ++ 03-,C ++ 11-,C ++ 14-或C ++ 1z兼容编译器。这在处理落后于标准的编译器时,或者在编译没有C ++ 11兼容编译器的平台时非常有用。
有关void_t
的详细信息,请参阅cppreference。