我试图弄清楚这个SFINAE概念,我必须说我觉得它真的很混乱。我理解为什么如果编译器可以根据模板类型/ args推断并选择正确的函数,这将是一个巨大的优势,但我不知道那是不是SFINAE,因为缩写代表的含义是什么意思否则IMO。也许你可以为我排序,但现在这不是我的问题。
我的问题是这样的:我抬起头来试试SFINAE的例子: https://en.cppreference.com/w/cpp/language/sfinae
特别是告诉您模板类型C是对象类型还是内在类型(int,bool等)的那个。我所谈论的示例代码是:
[['F', 0, 0, 0, 1],
['T', 0, 3, 0, 4],
['B', 1, 1, 0, 1],
['K', 2, 2, 0, 1],
['J', 2, 4, 0, 2],
['Bxx', 4, 4, 0, 1]]
然后我想尝试一下,所以我稍微修改了它,改变了可能影响使用哪个函数的任何东西,最后得到了以下代码。我使用Visual Studio 2017.我不认为它运行C ++ 2017,但它不会落后。毋庸置疑,它表明"不是阶级"两次:
template<typename T>
class is_class {
typedef char yes[1];
typedef char no [2];
template<typename C> static yes& test(int C::*); // selected if C is a class type
template<typename C> static no& test(...); // selected otherwise
public:
static bool const value = sizeof(test<T>(0)) == sizeof(yes);
};
造成这种情况的原因是什么?你能不能尽可能少地改变它并使它工作&#34;也就是说,使#include<cstdio>
#define say(x) printf(#x "\n")
template<typename T>
void f(int T::*) {
printf("f<T>();\n");
}
template<class T>
void f(T) {
printf("normal f();\n");
}
class Hejsa {
public:
int A;
Hejsa() { A = 2; }
};
template<typename T>
class is_class {
public:
typedef char yes[1];
typedef char no[2];
template<typename C> static yes& test(int C::*) {
say("is class"); return new yes;
}; // selected if C is a class type
template<typename C> static no& test(...) {
say("is not class"); no _n; return _n;
}; // selected otherwise
public:
static bool const value = sizeof(test<T>(0)) == sizeof(yes);
};
int main() {
int var1 = 9;
Hejsa var2;
is_class<Hejsa>::test<Hejsa>(var1);
is_class<Hejsa>::test<Hejsa>(var2);
f(var1);
f(var2);
getchar();
}
说&#34;不是等级&#34;并且test<Hejsa>(var1);
说&#34;是班级&#34;? (&#39; Hejsa&#39;是一个丹麦语,意思是“Hiya&#39;或者#39; Hello there&#39;,类似的东西; P)
提前致谢,
托马斯
答案 0 :(得分:1)
SFINAE如何创造这些特征:
首先,简单的typedef确保不同的大小
typedef char yes[1]; // sizeof == 1
typedef char no [2]; // sizeof != 1 (2 actually)
然后,2个重载函数(声明):
template<typename C> static yes& test(int C::*);
template<typename C> static no& test(...);
int C::*
是int
类型成员的指针。只有当C
是一个类时,它才能很好地形成(即使该类没有类型为int
BTW的成员)。对于其他类型(如float
),它是不正确的。
...
是接受额外参数的省略号(如printf
系列)。
因此当C
是一个类时,这两个函数对test<C>(0)
yes& test(int C::* m); // with m = nullptr
no& test(...); // with ... taking int 0
选择第一个重载决策规则。 (所以返回类型为yes&
)。
但C
不是一个类(比如说float
)
对于template<typename C> static yes& test(int C::*);
,替换
我们得到yes& test(int float::*);
由于该功能是模板而失败取决于模板, 我们只是在重载集中忽略该函数(不是错误),而不是出错。
只有省略号功能对test<C>(0)
仍然有效(因此返回类型为no&
)。
现在,使用sizeof(test<T>(0))
允许向编译器询问它将选择哪个重载(不调用函数,因此不需要定义)。
我们将最终结果存储在静态成员is_class::value
中。
一旦有了特征,可能的用法包括重载中的标签调度或直接SFINAE:
template <typename T>
std::enable_if_t<is_class<T>::value> foo() { std::cout << "a class"; }
template <typename T>
std::enable_if_t<!is_class<T>::value> foo() { std::cout << "a class"; }
当bool为false时, std::enable_if_t<bool>
生成错误,否则被void
替换。
答案 1 :(得分:1)
从您的代码中,我会说您混淆了SFINAE和重载决策(可能因为cppreference.com在示例中使用了两者)。
重载分辨率是编译器用于选择要调用的函数的规则。因此,它允许您根据传递的类型调用不同的函数。
SFINAE将允许您测试是否可以对您不熟悉的类型进行操作。通常,测试类型是否具有给定的成员函数。
通常情况下,您无法编写测试代码,因为调用不存在的成员函数是错误的。但是,在应用重载决策规则时,编译器将丢弃一个会导致编译错误的函数。 编译成功,因为省略号重载是一种全部捕获。
现在,SFINAE的兴趣在于,如果你的类型具有给定的成员函数,编译器将选择更精确的函数(省略号是最后的手段)。使用函数的不同返回类型,可以检测选择了哪个重载。