向非C ++程序员解释C ++ SFINAE [英] Explain C++ SFINAE to a non-C++ programmer

查看:116
本文介绍了向非C ++程序员解释C ++ SFINAE的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

什么是CINA中的SFINAE?

What is SFINAE in C++?

你能用一个不懂C ++的程序员解释一下吗?另外,像Python这样的语言中的什么概念SFINAE对应于

Can you please explain it in words understandable to a programmer who is not versed in C++? Also, what concept in a language like Python does SFINAE correspond to?

推荐答案

警告: em>长解释,但希望它真的解释不仅SFINAE的作用,但是给你一些想法,什么时候和为什么你可以使用它。

Warning: this is a really long explanation, but hopefully it really explains not only what SFINAE does, but gives some idea of when and why you might use it.

好,为了解释这个可能需要备份和解释模板一点。众所周知,Python使用通常所说的鸭式类型 - 例如,当你调用一个函数时,只要X提供了函数所使用的所有操作,你就可以将一个对象X传递给该函数。

Okay, to explain this we probably need to back up and explain templates a bit. As we all know, Python uses what's commonly referred to as duck typing -- for example, when you invoke a function, you can pass an object X to that function as long as X provides all the operations used by the function.

在C ++中,正常(非模板)函数需要指定参数的类型。如果你定义了一个函数:

In C++, a normal (non-template) function requires that you specify the type of a parameter. If you defined a function like:

int plus1(int x) { return x + 1; }

您只能将此函数应用于 int 。以可以的方式使用 x 的事实也适用于其他类型,例如 long float 没有什么区别 - 它只适用于 int

You can only apply that function to an int. The fact that it uses x in a way that could just as well apply to other types like long or float makes no difference -- it only applies to an int anyway.

要想更接近Python的鸭子类型,你可以创建一个模板:

To get something closer to Python's duck typing, you can create a template instead:

template <class T>
T plus1(T x) { return x + 1; }

现在我们的 plus1 就像它在Python中一样 - 特别是,对于 x + 1 的任何类型的对象 x code>被定义。

Now our plus1 is a lot more like it would be in Python -- in particular, we can invoke it equally well to an object x of any type for which x + 1 is defined.

现在,例如,考虑我们要将一些对象写入流。不幸的是,其中一些对象使用 stream<<对象,但其他人使用 object.write(stream); 。我们想要能够处理任一个而无需用户指定哪个。现在,模板专门化允许我们编写专门的模板,所以如果是 object.write(stream)语法的可以这样做:

Now, consider, for example, that we want to write some objects out to a stream. Unfortunately, some of those objects get written to a stream using stream << object, but others use object.write(stream); instead. We want to be able to handle either one without the user having to specify which. Now, template specialization allows us to write the specialized template, so if it was one type that used the object.write(stream) syntax, we could do something like:

template <class T>
std::ostream &write_object(T object, std::ostream &os) {
    return os << object;
}

template <>
std::ostream &write_object(special_object object, std::ostream &os) { 
    return object.write(os);
}

这对于一种类型来说没问题,如果我们想要足够糟糕,更多专用于全部不支持流<<对象 - 但是一旦(例如)用户添加了不支持 stream<<

That's fine for one type, and if we wanted to badly enough we could add more specializations for all the types that don't support stream << object -- but as soon as (for example) the user adds a new type that doesn't support stream << object, things break again.

我们想要的是对任何支持的对象使用第一个专门化的方法。流<<对象; ,但第二个任何东西(虽然我们可能有时想添加第三个对象使用 x.print(stream);

What we want is a way to use the first specialization for any object that supports stream << object;, but the second for anything else (though we might sometime want to add a third for objects that use x.print(stream); instead).

我们可以使用SFINAE来做决定。要做到这一点,我们通常依赖于几个其他古怪的C ++的细节。一个是使用 sizeof 运算符。 sizeof 确定类型或表达式的大小,但是它在编译时完全通过查看所涉及的类型完成,而不评估表达式本身。例如,如果我有类似的:

We can use SFINAE to make that determination. To do that, we typically rely on a couple of other oddball details of C++. One is to use the sizeof operator. sizeof determines the size of a type or an expression, but it does so entirely at compile time by looking at the types involved, without evaluating the expression itself. For example, if I have something like:

int func() { return -1; }

我可以使用 sizeof(func())。在这种情况下, func()返回 int ,因此 sizeof(func 相当于 sizeof(int)

I can use sizeof(func()). In this case, func() returns an int, so sizeof(func()) is equivalent to sizeof(int).

是数组的大小必须为正,为零的事实。

The second interesting item that's frequently used is the fact that the size of an array must to be positive, not zero.

现在,将这些组合在一起,我们可以做这个:

Now, putting those together, we can do something like this:

// stolen, more or less intact from: 
//     http://stackoverflow.com/questions/2127693/sfinae-sizeof-detect-if-expression-compiles
template<class T> T& ref();
template<class T> T  val();

template<class T>
struct has_inserter
{
    template<class U> 
    static char test(char(*)[sizeof(ref<std::ostream>() << val<U>())]);

    template<class U> 
    static long test(...);

    enum { value = 1 == sizeof test<T>(0) };
    typedef boost::integral_constant<bool, value> type;
};

这里我们有两个重载 test 。其中第二个采用可变参数列表( ... ),这意味着它可以匹配任何类型 - 但它也是编译器在选择重载,因此只有 匹配,如果第一个 test 的另一个重载更有趣:它定义了一个函数,它接受一个参数:一个指向函数的指针数组,返回 char ,其中数组的大小(实质上) sizeof(stream<<< object)。如果 stream<<对象不是有效的表达式, sizeof 将产生0,这意味着我们创建了一个大小为零的数组,允许。这是SFINAE本身出现的地方。试图替换不支持运算符< U 的类型将失败,因为它会产生零大小的数组。但是,这不是一个错误 - 它只是意味着函数从重载集中消除。因此,另一个函数是在这种情况下可以使用的唯一的函数。

Here we have two overloads of test. The second of these takes a variable argument list (the ...) which means it can match any type -- but it's also the last choice the compiler will make in selecting an overload, so it'll only match if the first one does not. The other overload of test is a bit more interesting: it defines a function that takes one parameter: an array of pointers to functions that return char, where the size of the array is (in essence) sizeof(stream << object). If stream << object isn't a valid expression, the sizeof will yield 0, which means we've created an array of size zero, which isn't allowed. This is where the SFINAE itself comes into the picture. Attempting to substitute the type that doesn't support operator<< for U would fail, because it would produce a zero-sized array. But, that's not an error -- it just means that function is eliminated from the overload set. Therefore, the other function is the only one that can be used in such a case.

然后在枚举 expression下面 - 它查看从 test 的选定重载的返回值,并检查它是否等于1(如果是,则意味着函数返回 char ,但是否则选择返回 long 的函数)。

That then gets used in the enum expression below -- it looks at the return value from the selected overload of test and checks whether it's equal to 1 (if it is, it means the function returning char was selected, but otherwise, the function returning long was selected).

结果是 has_inserter< type> :: value 将会是 l code> some_ostream<<对象; 将编译, 0 如果不会。然后,我们可以使用该值来控制模板专用化,以选择正确的方式写出特定类型的值。

The result is that has_inserter<type>::value will be l if we could use some_ostream << object; would compile, and 0 if it wouldn't. We can then use that value to control template specialization to pick the right way to write out the value for a particular type.

这篇关于向非C ++程序员解释C ++ SFINAE的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆