Explain C++ SFINAE to a non-C++ programmer

前端 未结 5 1050
孤城傲影
孤城傲影 2020-11-27 10:29

What is SFINAE in C++?

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

5条回答
  •  借酒劲吻你
    2020-11-27 10:50

    SFINAE is a principle a C++ compiler uses to filter out some templated function overloads during overload resolution (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  void f(T);                 //1
    template  void f(T*);                //2
    template  void f(std::complex);   //3
    

    resolving f((int)1) will remove versions 2 and three, because int is not equal to complex or T* for some T. Similarly, f(std::complex(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.

    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).

    If you have done some declarative programming before, this mechanism is similar to (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:

    template  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):

    1. the compiler matches T against char which yields trivially T as char
    2. the compiler substitutes all the Ts in the declaration as chars. This yields void f(char t, int(*)[sizeof(char)-sizeof(int)] = 0).
    3. 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).
    4. 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.

    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.

    I won't write an extensive list of SFINAE errors, but some of the most popular are:

    • selecting a nested type of a type that doesn't have it. eg. typename T::type for T = int or T = A where A is a class without a nested type called type.
    • creating an array type of nonpositive size. For an example, see this litb's answer
    • creating a member pointer to a type that's not a class. eg. 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++.


    1: or partial template specializations when talking about class templates

提交回复
热议问题