What is SFINAE in C++?
在c++中,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?
你能不能用不懂c++的程序员可以理解的语言来解释一下?此外,在像Python这样的语言中,SFINAE对应的概念是什么?
5 个解决方案
#1
97
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.
警告:这是一个很长的解释,但希望它不仅能解释SFINAE的功能,而且能提供一些关于何时以及为什么使用它的想法。
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.
好的,为了解释这个,我们可能需要备份并解释一些模板。众所周知,Python使用通常称为duck typing的东西——例如,当您调用一个函数时,只要X提供函数所使用的所有操作,就可以将对象X传递给该函数。
In C++, a normal (non-template) function requires that you specify the type of a parameter. If you defined a function like:
在c++中,一个普通(非模板)函数需要指定参数的类型。如果定义的函数为:
int plus1(int x) { return x + 1; }
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.
您只能将该函数应用到int中,它使用x的方式同样适用于其他类型,比如long或float,这一点没有区别——它只适用于int。
To get something closer to Python's duck typing, you can create a template instead:
为了更接近Python的duck typing,您可以创建一个模板:
template <class T>
T plus1(T x) { return x + 1; }
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.
现在,我们的plus1更像是在Python中——特别是,我们可以把它同样地调用到任何类型的对象x中,x + 1被定义了。
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:
现在,考虑一下,例如,我们想要将一些对象写到流中。不幸的是,其中一些对象使用流< <对象写入流,而另一些使用object.write(流);代替。我们希望能够处理任何一个,而不需要用户指定哪个。现在,模板专门化允许我们编写专门的模板,所以如果它是一个使用object.write(stream)语法的类型,我们可以这样做:< p>
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);
}
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.
对于一种类型来说,这是可以的,如果我们非常希望为所有不支持流<< object的类型添加更多的专门化——但是一旦(例如)用户添加了不支持流<< < object的新类型,事情就会再次发生。
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).
我们想要的是对任何支持stream << object的对象使用第一种专门化,而对其他对象使用第二种专门化(尽管我们有时可能想为使用x.print(stream)的对象添加第三种专门化);相反)。
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:
我们可以用SFINAE来做这个决定。要做到这一点,我们通常依赖于c++的其他一些奇怪的细节。一个是使用sizeof运算符。sizeof决定了一个类型或一个表达式的大小,但是它完全是在编译时通过查看所涉及的类型来实现的,而不计算表达式本身。例如,如果我有
int func() { return -1; }
I can use sizeof(func())
. In this case, func()
returns an int
, so sizeof(func())
is equivalent to sizeof(int)
.
我可以使用sizeof(func())。在本例中,func()返回一个int,因此sizeof(func())等价于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://*.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;
};
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.
这里我们有两个重载的测试。第二种方法采用变量参数列表(The…),这意味着它可以匹配任何类型——但它也是编译器在选择重载时要做的最后一个选择,因此只有当第一个参数不匹配时,它才会匹配。测试的另一个重载更有趣:它定义了一个函数,该函数接受一个参数:返回char的指针数组,其中数组的大小(本质上)是sizeof(stream < object)。如果stream << object不是一个有效的表达式,那么sizeof将会产生0,这意味着我们已经创建了一个大小为0的数组,这是不允许的。这就是SFINAE本身进入画面的地方。尝试替换不支持操作符<< for U的类型将失败,因为它将生成一个零大小的数组。但是,这并不是一个错误,它只是意味着函数在重载集中被排除,因此,在这种情况下,另一个函数是唯一可以使用的函数。
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).
然后在下面的enum表达式中使用它——它从选中的测试重载中查看返回值,并检查它是否等于1(如果是的话,它意味着选择返回char的函数,但如果不是,则选择返回long的函数)。
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.
结果是has_inserter
#2
10
If you have some overloaded template functions, some of the possible candidates for use may fail to be compilable when template substitution is performed, because the thing being substituted may not have the correct behaviour. This is not considered to be a programming error, the failed templates are simply removed from the set available for that particular parameter.
如果您有一些重载的模板函数,那么在执行模板替换时,一些可能使用的候选函数可能无法编译,因为被替换的对象可能没有正确的行为。这并不被认为是一个编程错误,失败的模板只是从该特定参数可用的集合中删除。
I have no idea if Python has a similar feature, and don't really see why a non-C++ programmer should care about this feature. But if you want to learn more about templates, the best book on them is C++ Templates: The Complete Guide.
我不知道Python是否有类似的特性,也不明白为什么非c++程序员应该关心这个特性。但是如果你想了解更多关于模板的知识,最好的书是c++模板:完整的指南。
#3
7
SFINAE is a principle a C++ compiler uses to filter out some templated function overloads during overload resolution (1)
SFINAE是c++编译器在重载解析过程中过滤掉某些模板化函数过载的原则(1)
When the compiler resolves a particular function call, it considers a set of available function and function template declarations to find out which one will be used. Basically, there are two mechanisms to do it. One can be described as syntactic. Given declarations:
当编译器解析一个特定的函数调用时,它会考虑一组可用的函数和函数模板声明,以找出将使用哪个函数模板声明。基本上,有两种机制可以做到这一点。一个可以被描述为句法。鉴于声明:
template <class T> void f(T); //1
template <class T> void f(T*); //2
template <class T> void f(std::complex<T>); //3
resolving f((int)1)
will remove versions 2 and three, because int
is not equal to complex<T>
or T*
for some T
. Similarly, f(std::complex<float>(1))
would remove the second variant and f((int*)&x)
would remove the third. The compiler does this by trying to deduce the template parameters from the function arguments. If deduction fails (as in T*
against int
), the overload is discarded.
解析f((int)1)将删除版本2和版本3,因为对于某些T, int不等于复杂的
The reason we want this is obvious - we may want to do slightly different things for different types (eg. an absolute value of a complex is computed by x*conj(x)
and yields a real number, not a complex number, which is different from the computation for floats).
我们想要这个的原因很明显——我们可能想为不同的类型做一些稍微不同的事情。复数的绝对值由x*conj(x)计算,并产生一个实数,而不是复数,这与浮点数的计算不同。
If you have done some declarative programming before, this mechanism is similar to (Haskell):
如果您以前做过一些声明式编程,这种机制类似于(Haskell):
f Complex x y = ...
f _ = ...
The way C++ takes this further is that the deduction may fail even when the deduced types are OK, but back substitution into the other yield some "nonsensical" result (more on that later). For example:
c++进一步采用这种方法的方法是,即使推导出的类型是正确的,推导也可能失败,但是将反向替换为另一个类型会产生一些“荒谬的”结果(后面会详细介绍)。例如:
template <class T> void f(T t, int(*)[sizeof(T)-sizeof(int)] = 0);
when deducing f('c')
(we call with a single argument, because the second argument is implicit):
在推导f('c')时(我们用一个参数调用,因为第二个参数是隐式的):
- the compiler matches
T
againstchar
which yields triviallyT
aschar
- 编译器将T与char匹配,后者会产生微不足道的T作为char
- the compiler substitutes all the
T
s in the declaration aschar
s. This yieldsvoid f(char t, int(*)[sizeof(char)-sizeof(int)] = 0)
. - 编译器将声明中的所有Ts替换为chars。这将产生void f(char t, int(*)[sizeof(char)-sizeof(int)] = 0)。
- The type of the second argument is pointer to array
int [sizeof(char)-sizeof(int)]
. The size of this array may be eg. -3 (depending on your platform). - 第二个参数的类型是指向数组int [sizeof(char)-sizeof(int)]的指针。这个数组的大小可以是eg。-3(取决于你的平台)。
- Arrays of length
<= 0
are invalid, so the compiler discards the overload. Substitution Failure Is Not An Error, the compiler won't reject the program. - 长度<= 0的数组无效,因此编译器丢弃了重载。替换失败不是错误,编译器不会拒绝程序。
In the end, if more than one function overload remains, the compiler uses conversion sequences comparison and partial ordering of templates to select one that is the "best".
最后,如果仍然有多个函数重载,编译器将使用转换序列比较和模板的部分排序来选择一个“最佳”的模板。
There are more such "nonsensical" results that work like this, they are enumerated in a list in the standard (C++03). In C++0x, the realm of SFINAE is extended to almost any type error.
有更多这样的“荒谬的”结果可以像这样工作,它们在标准(c++ 03)中的列表中列出。在c++ 0x中,SFINAE领域扩展到几乎所有类型错误。
I won't write an extensive list of SFINAE errors, but some of the most popular are:
我不会列出大量的SFINAE错误,但最流行的是:
- selecting a nested type of a type that doesn't have it. eg.
typename T::type
forT = int
orT = A
whereA
is a class without a nested type calledtype
. - 选择没有类型的嵌套类型。如。类型为T = int或T = A,其中A是一个没有称为type的嵌套类型的类。
- creating an array type of nonpositive size. For an example, see this litb's answer
- 创建非正数大小的数组类型。例如,请参阅这个litb的答案
- creating a member pointer to a type that's not a class. eg.
int C::*
forC = int
- 创建一个不是类的成员指针。如。int C:::* for C = int
This mechanism is not similar to anything in other programming languages I know of. If you were to do a similar thing in Haskell, you'd use guards which are more powerful, but impossible in C++.
这种机制与我所知道的其他编程语言中的任何机制都不相似。如果你在Haskell中做类似的事情,你会使用更强大的警卫,但是在c++中是不可能的。
1: or partial template specializations when talking about class templates
在讨论类模板时,或部分模板专门化
#4
5
Python won't help you at all. But you do say you're already basically familiar with templates.
Python对你毫无帮助。但是你说你已经基本熟悉模板了。
The most fundamental SFINAE construct is usage of enable_if
. The only tricky part is that class enable_if
does not encapsulate SFINAE, it merely exposes it.
最基本的SFINAE构造是enable_if的使用。惟一棘手的部分是类enable_if没有封装SFINAE,它只是公开它。
template< bool enable >
class enable_if { }; // enable_if contains nothing…
template<>
class enable_if< true > { // … unless argument is true…
public:
typedef void type; // … in which case there is a dummy definition
};
template< bool b > // if "b" is true,
typename enable_if< b >::type function() {} //the dummy exists: success
template< bool b >
typename enable_if< ! b >::type function() {} // dummy does not exist: failure
/* But Substitution Failure Is Not An Error!
So, first definition is used and second, although redundant and
nonsensical, is quietly ignored. */
int main() {
function< true >();
}
In SFINAE, there is some structure which sets up an error condition (class enable_if
here) and a number of parallel, otherwise conflicting definitions. Some error occurs in all but one definition, which the compiler picks and uses without complaining about the others.
在SFINAE中,有一些结构设置了一个错误条件(如果这里是class enable_if),还有一些并行的、相互冲突的定义。除了一个定义外,所有的定义都出现了一些错误,编译器选择并使用这些定义而不抱怨其他定义。
What kinds of errors are acceptable is a major detail which has only recently been standardized, but you don't seem to be asking about that.
什么类型的错误是可以接受的,这是最近才被标准化的一个主要细节,但是你似乎没有问这个问题。
#5
3
There is nothing in Python that remotely resembles SFINAE. Python has no templates, and certainly no parameter-based function resolution as occurs when resolving template specialisations. Function lookup is done purely by name in Python.
在Python中,没有任何东西与SFINAE稍有相似之处。Python没有模板,当然也没有解析模板专门化时出现的基于参数的函数解析。在Python中,函数查找完全是通过名称完成的。
#1
97
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.
警告:这是一个很长的解释,但希望它不仅能解释SFINAE的功能,而且能提供一些关于何时以及为什么使用它的想法。
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.
好的,为了解释这个,我们可能需要备份并解释一些模板。众所周知,Python使用通常称为duck typing的东西——例如,当您调用一个函数时,只要X提供函数所使用的所有操作,就可以将对象X传递给该函数。
In C++, a normal (non-template) function requires that you specify the type of a parameter. If you defined a function like:
在c++中,一个普通(非模板)函数需要指定参数的类型。如果定义的函数为:
int plus1(int x) { return x + 1; }
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.
您只能将该函数应用到int中,它使用x的方式同样适用于其他类型,比如long或float,这一点没有区别——它只适用于int。
To get something closer to Python's duck typing, you can create a template instead:
为了更接近Python的duck typing,您可以创建一个模板:
template <class T>
T plus1(T x) { return x + 1; }
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.
现在,我们的plus1更像是在Python中——特别是,我们可以把它同样地调用到任何类型的对象x中,x + 1被定义了。
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:
现在,考虑一下,例如,我们想要将一些对象写到流中。不幸的是,其中一些对象使用流< <对象写入流,而另一些使用object.write(流);代替。我们希望能够处理任何一个,而不需要用户指定哪个。现在,模板专门化允许我们编写专门的模板,所以如果它是一个使用object.write(stream)语法的类型,我们可以这样做:< p>
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);
}
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.
对于一种类型来说,这是可以的,如果我们非常希望为所有不支持流<< object的类型添加更多的专门化——但是一旦(例如)用户添加了不支持流<< < object的新类型,事情就会再次发生。
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).
我们想要的是对任何支持stream << object的对象使用第一种专门化,而对其他对象使用第二种专门化(尽管我们有时可能想为使用x.print(stream)的对象添加第三种专门化);相反)。
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:
我们可以用SFINAE来做这个决定。要做到这一点,我们通常依赖于c++的其他一些奇怪的细节。一个是使用sizeof运算符。sizeof决定了一个类型或一个表达式的大小,但是它完全是在编译时通过查看所涉及的类型来实现的,而不计算表达式本身。例如,如果我有
int func() { return -1; }
I can use sizeof(func())
. In this case, func()
returns an int
, so sizeof(func())
is equivalent to sizeof(int)
.
我可以使用sizeof(func())。在本例中,func()返回一个int,因此sizeof(func())等价于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://*.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;
};
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.
这里我们有两个重载的测试。第二种方法采用变量参数列表(The…),这意味着它可以匹配任何类型——但它也是编译器在选择重载时要做的最后一个选择,因此只有当第一个参数不匹配时,它才会匹配。测试的另一个重载更有趣:它定义了一个函数,该函数接受一个参数:返回char的指针数组,其中数组的大小(本质上)是sizeof(stream < object)。如果stream << object不是一个有效的表达式,那么sizeof将会产生0,这意味着我们已经创建了一个大小为0的数组,这是不允许的。这就是SFINAE本身进入画面的地方。尝试替换不支持操作符<< for U的类型将失败,因为它将生成一个零大小的数组。但是,这并不是一个错误,它只是意味着函数在重载集中被排除,因此,在这种情况下,另一个函数是唯一可以使用的函数。
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).
然后在下面的enum表达式中使用它——它从选中的测试重载中查看返回值,并检查它是否等于1(如果是的话,它意味着选择返回char的函数,但如果不是,则选择返回long的函数)。
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.
结果是has_inserter
#2
10
If you have some overloaded template functions, some of the possible candidates for use may fail to be compilable when template substitution is performed, because the thing being substituted may not have the correct behaviour. This is not considered to be a programming error, the failed templates are simply removed from the set available for that particular parameter.
如果您有一些重载的模板函数,那么在执行模板替换时,一些可能使用的候选函数可能无法编译,因为被替换的对象可能没有正确的行为。这并不被认为是一个编程错误,失败的模板只是从该特定参数可用的集合中删除。
I have no idea if Python has a similar feature, and don't really see why a non-C++ programmer should care about this feature. But if you want to learn more about templates, the best book on them is C++ Templates: The Complete Guide.
我不知道Python是否有类似的特性,也不明白为什么非c++程序员应该关心这个特性。但是如果你想了解更多关于模板的知识,最好的书是c++模板:完整的指南。
#3
7
SFINAE is a principle a C++ compiler uses to filter out some templated function overloads during overload resolution (1)
SFINAE是c++编译器在重载解析过程中过滤掉某些模板化函数过载的原则(1)
When the compiler resolves a particular function call, it considers a set of available function and function template declarations to find out which one will be used. Basically, there are two mechanisms to do it. One can be described as syntactic. Given declarations:
当编译器解析一个特定的函数调用时,它会考虑一组可用的函数和函数模板声明,以找出将使用哪个函数模板声明。基本上,有两种机制可以做到这一点。一个可以被描述为句法。鉴于声明:
template <class T> void f(T); //1
template <class T> void f(T*); //2
template <class T> void f(std::complex<T>); //3
resolving f((int)1)
will remove versions 2 and three, because int
is not equal to complex<T>
or T*
for some T
. Similarly, f(std::complex<float>(1))
would remove the second variant and f((int*)&x)
would remove the third. The compiler does this by trying to deduce the template parameters from the function arguments. If deduction fails (as in T*
against int
), the overload is discarded.
解析f((int)1)将删除版本2和版本3,因为对于某些T, int不等于复杂的
The reason we want this is obvious - we may want to do slightly different things for different types (eg. an absolute value of a complex is computed by x*conj(x)
and yields a real number, not a complex number, which is different from the computation for floats).
我们想要这个的原因很明显——我们可能想为不同的类型做一些稍微不同的事情。复数的绝对值由x*conj(x)计算,并产生一个实数,而不是复数,这与浮点数的计算不同。
If you have done some declarative programming before, this mechanism is similar to (Haskell):
如果您以前做过一些声明式编程,这种机制类似于(Haskell):
f Complex x y = ...
f _ = ...
The way C++ takes this further is that the deduction may fail even when the deduced types are OK, but back substitution into the other yield some "nonsensical" result (more on that later). For example:
c++进一步采用这种方法的方法是,即使推导出的类型是正确的,推导也可能失败,但是将反向替换为另一个类型会产生一些“荒谬的”结果(后面会详细介绍)。例如:
template <class T> void f(T t, int(*)[sizeof(T)-sizeof(int)] = 0);
when deducing f('c')
(we call with a single argument, because the second argument is implicit):
在推导f('c')时(我们用一个参数调用,因为第二个参数是隐式的):
- the compiler matches
T
againstchar
which yields triviallyT
aschar
- 编译器将T与char匹配,后者会产生微不足道的T作为char
- the compiler substitutes all the
T
s in the declaration aschar
s. This yieldsvoid f(char t, int(*)[sizeof(char)-sizeof(int)] = 0)
. - 编译器将声明中的所有Ts替换为chars。这将产生void f(char t, int(*)[sizeof(char)-sizeof(int)] = 0)。
- The type of the second argument is pointer to array
int [sizeof(char)-sizeof(int)]
. The size of this array may be eg. -3 (depending on your platform). - 第二个参数的类型是指向数组int [sizeof(char)-sizeof(int)]的指针。这个数组的大小可以是eg。-3(取决于你的平台)。
- Arrays of length
<= 0
are invalid, so the compiler discards the overload. Substitution Failure Is Not An Error, the compiler won't reject the program. - 长度<= 0的数组无效,因此编译器丢弃了重载。替换失败不是错误,编译器不会拒绝程序。
In the end, if more than one function overload remains, the compiler uses conversion sequences comparison and partial ordering of templates to select one that is the "best".
最后,如果仍然有多个函数重载,编译器将使用转换序列比较和模板的部分排序来选择一个“最佳”的模板。
There are more such "nonsensical" results that work like this, they are enumerated in a list in the standard (C++03). In C++0x, the realm of SFINAE is extended to almost any type error.
有更多这样的“荒谬的”结果可以像这样工作,它们在标准(c++ 03)中的列表中列出。在c++ 0x中,SFINAE领域扩展到几乎所有类型错误。
I won't write an extensive list of SFINAE errors, but some of the most popular are:
我不会列出大量的SFINAE错误,但最流行的是:
- selecting a nested type of a type that doesn't have it. eg.
typename T::type
forT = int
orT = A
whereA
is a class without a nested type calledtype
. - 选择没有类型的嵌套类型。如。类型为T = int或T = A,其中A是一个没有称为type的嵌套类型的类。
- creating an array type of nonpositive size. For an example, see this litb's answer
- 创建非正数大小的数组类型。例如,请参阅这个litb的答案
- creating a member pointer to a type that's not a class. eg.
int C::*
forC = int
- 创建一个不是类的成员指针。如。int C:::* for C = int
This mechanism is not similar to anything in other programming languages I know of. If you were to do a similar thing in Haskell, you'd use guards which are more powerful, but impossible in C++.
这种机制与我所知道的其他编程语言中的任何机制都不相似。如果你在Haskell中做类似的事情,你会使用更强大的警卫,但是在c++中是不可能的。
1: or partial template specializations when talking about class templates
在讨论类模板时,或部分模板专门化
#4
5
Python won't help you at all. But you do say you're already basically familiar with templates.
Python对你毫无帮助。但是你说你已经基本熟悉模板了。
The most fundamental SFINAE construct is usage of enable_if
. The only tricky part is that class enable_if
does not encapsulate SFINAE, it merely exposes it.
最基本的SFINAE构造是enable_if的使用。惟一棘手的部分是类enable_if没有封装SFINAE,它只是公开它。
template< bool enable >
class enable_if { }; // enable_if contains nothing…
template<>
class enable_if< true > { // … unless argument is true…
public:
typedef void type; // … in which case there is a dummy definition
};
template< bool b > // if "b" is true,
typename enable_if< b >::type function() {} //the dummy exists: success
template< bool b >
typename enable_if< ! b >::type function() {} // dummy does not exist: failure
/* But Substitution Failure Is Not An Error!
So, first definition is used and second, although redundant and
nonsensical, is quietly ignored. */
int main() {
function< true >();
}
In SFINAE, there is some structure which sets up an error condition (class enable_if
here) and a number of parallel, otherwise conflicting definitions. Some error occurs in all but one definition, which the compiler picks and uses without complaining about the others.
在SFINAE中,有一些结构设置了一个错误条件(如果这里是class enable_if),还有一些并行的、相互冲突的定义。除了一个定义外,所有的定义都出现了一些错误,编译器选择并使用这些定义而不抱怨其他定义。
What kinds of errors are acceptable is a major detail which has only recently been standardized, but you don't seem to be asking about that.
什么类型的错误是可以接受的,这是最近才被标准化的一个主要细节,但是你似乎没有问这个问题。
#5
3
There is nothing in Python that remotely resembles SFINAE. Python has no templates, and certainly no parameter-based function resolution as occurs when resolving template specialisations. Function lookup is done purely by name in Python.
在Python中,没有任何东西与SFINAE稍有相似之处。Python没有模板,当然也没有解析模板专门化时出现的基于参数的函数解析。在Python中,函数查找完全是通过名称完成的。