SFINAE 示例不起作用

SFINAE example not working

我正在努力弄清楚这个 SFINAE 概念,我必须说我觉得它真的很令人困惑。我理解为什么如果编译器可以根据模板 types/args 推断并选择正确的函数,那将是一个巨大的优势,但我不知道那是否是 SFINAE,因为首字母缩略词代表的含义与 IMO 不同。也许你可以帮我解决这个问题,但现在这实际上不是我的问题。

我的问题是:我从这里查找并尝试了一个 SFINAE 示例: https://en.cppreference.com/w/cpp/language/sfinae

特别是告诉您模板类型 C 是对象类型还是内部类型(int、bool 等)的那个。我正在谈论的示例代码是这样的:

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);
};

然后我想尝试一下,所以我稍微修改了一下,没有改变任何可能影响使用哪个函数的东西,最后得到了以下代码。我用的是Visual Studio 2017。我不认为它运行C++2017,但也不能落后太多。不用说了,两次显示"is not class":

#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();
}

这是什么原因造成的?能不能尽量少改,改成"work",也就是让test<Hejsa>(var1);说"is not class",test<Hejsa>(var2)说"is class"? ('Hejsa' 是一个丹麦语单词,意思是 'Hiya' 或 'Hello there',类似的意思;P)

提前致谢,

托马斯

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 是 class 时(即使 class 没有类型 int BTW 的成员),它的格式才正确。对于其他类型(如 float),它的格式不正确。

... 是接受额外参数的省略号(如 printf 系列)。

所以当 C 是 class 时,两个函数都对 test<C>(0)

有效
yes& test(int C::* m); // with m = nullptr
no&  test(...);        // with ... taking int 0

超载解析规则做第一个被选中。 (所以 return 类型是 yes&)。

但是当 C 不是 class(比如说 float

对于template<typename C> static yes& test(int C::*);,用替换 我们会得到 yes& test(int float::*);

由于函数是模板,失败取决于模板, 我们没有出现错误,而是简单地忽略了重载集中的那个函数(它不是错误)。

只有省略号函数对 test<C>(0) 有效(因此 return 类型为 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"; }

std::enable_if_t<bool> 在 bool 为假时格式错误,否则由 void 替代。

根据您的代码,我会说您混淆了 SFINAE 和重载解析(可能是因为 cppreference.com 在示例中同时使用了两者)。

重载决议是编译器在选择要调用的函数时应用的规则。因此,它允许您根据传递的类型调用不同的函数。

SFINAE 将允许您测试是否可以对您不知道的类型进行操作。通常,测试类型是否具有给定的成员函数。

通常情况下,您无法编写测试它的代码,因为调用不存在的成员函数是错误的。但是,当应用重载决议规则时,编译器将丢弃一个会导致编译错误的函数。 编译成功,因为省略号重载是一种包罗万象。

现在,SFINAE 的兴趣在于,如果您的类型具有给定的成员函数,编译器将选择更精确的函数(省略号是最后的手段)。为函数使用不同的 return 类型,允许您检测选择了哪个重载。