SFINAE 有哪些好的用途?

发布于 2024-07-23 06:59:11 字数 69 浏览 16 评论 0原文

我想了解更多模板元编程。 我知道 SFINAE 代表“替换失败不是错误”。 但有人可以告诉我 SFINAE 的好用处吗?

I want to get into more template meta-programming. I know that SFINAE stands for "substitution failure is not an error." But can someone show me a good use for SFINAE?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

紧拥背影 2024-07-30 06:59:11

我喜欢使用 SFINAE 来检查布尔条件。

template<int I> void div(char(*)[I % 2 == 0] = 0) {
    /* this is taken when I is even */
}

template<int I> void div(char(*)[I % 2 == 1] = 0) {
    /* this is taken when I is odd */
}

它可能非常有用。 例如,我用它来检查使用运算符逗号收集的初始值设定项列表是否不超过固定大小。

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}

仅当 M 小于或等于 N 时才接受列表,这意味着初始值设定项列表没有太多元素。

语法 char(*)[C] 的意思是:指向元素类型为 char、大小为 C 的数组的指针。 如果 C 为 false(此处为 0),则我们得到无效类型 char(*)[0],指向零大小数组的指针:SFINAE 使得模板将被忽略。

boost::enable_if 表示,看起来像这样

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, 
           typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ }
}

在实践中,我经常发现检查条件的能力是一个有用的能力。

I like using SFINAE to check boolean conditions.

template<int I> void div(char(*)[I % 2 == 0] = 0) {
    /* this is taken when I is even */
}

template<int I> void div(char(*)[I % 2 == 1] = 0) {
    /* this is taken when I is odd */
}

It can be quite useful. For example, i used it to check whether an initializer list collected using operator comma is no longer than a fixed size

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}

The list is only accepted when M is smaller than or equal to N, which means that the initializer list has not too many elements.

The syntax char(*)[C] means: Pointer to an array with element type char and size C. If C is false (0 here), then we get the invalid type char(*)[0], pointer to a zero sized array: SFINAE makes it so that the template will be ignored then.

Expressed with boost::enable_if, that looks like this

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, 
           typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ }
}

In practice, i often find the ability to check conditions a useful ability.

以酷 2024-07-30 06:59:11

下面是一个示例(来自此处):

template<typename T>
class IsClassT {
  private:
    typedef char One;
    typedef struct { char a[2]; } Two;
    template<typename C> static One test(int C::*);
    // Will be chosen if T is anything except a class.
    template<typename C> static Two test(...);
  public:
    enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
    enum { No = !Yes };
};

IsClassT< ;int>::Yes 被求值,0 不能转换为 int int::* 因为 int 不是一个类,所以它不能有成员指针。 如果 SFINAE 不存在,那么您将收到编译器错误,例如“0 无法转换为非类类型 int 的成员指针”。 相反,它只使用 ... 形式返回 Two,因此计算结果为 false,int 不是类类型。

Heres one example (from here):

template<typename T>
class IsClassT {
  private:
    typedef char One;
    typedef struct { char a[2]; } Two;
    template<typename C> static One test(int C::*);
    // Will be chosen if T is anything except a class.
    template<typename C> static Two test(...);
  public:
    enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
    enum { No = !Yes };
};

When IsClassT<int>::Yes is evaluated, 0 cannot be converted to int int::* because int is not a class, so it can't have a member pointer. If SFINAE didn't exist, then you would get a compiler error, something like '0 cannot be converted to member pointer for non-class type int'. Instead, it just uses the ... form which returns Two, and thus evaluates to false, int is not a class type.

怂人 2024-07-30 06:59:11

在 C++11 中,SFINAE 测试变得更加漂亮。 以下是一些常见用法的示例:

根据特征选择函数重载

template<typename T>
std::enable_if_t<std::is_integral<T>::value> f(T t){
    //integral version
}
template<typename T>
std::enable_if_t<std::is_floating_point<T>::value> f(T t){
    //floating point version
}

使用所谓的类型接收器习惯用法,您可以对类型进行相当任意的测试,例如检查它是否有成员以及该成员是否属于某种类型

//this goes in some header so you can use it everywhere
template<typename T>
struct TypeSink{
    using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;

//use case
template<typename T, typename=void>
struct HasBarOfTypeInt : std::false_type{};
template<typename T>
struct HasBarOfTypeInt<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>> :
    std::is_same<typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type,int>{};


struct S{
   int bar;
};
struct K{

};

template<typename T, typename = TypeSinkT<decltype(&T::bar)>>
void print(T){
    std::cout << "has bar" << std::endl;
}
void print(...){
    std::cout << "no bar" << std::endl;
}

int main(){
    print(S{});
    print(K{});
    std::cout << "bar is int: " << HasBarOfTypeInt<S>::value << std::endl;
}

这里是一个实例:http://ideone.com/dHhyHE
我最近还在我的博客中写了关于 SFINAE 和标签调度的整个部分(无耻的插件但相关)

从 C++14 开始,有一个 std:: void_t 本质上与我的 TypeSink 相同。

In C++11 SFINAE tests have become much prettier. Here are a few examples of common uses:

Pick a function overload depending on traits

template<typename T>
std::enable_if_t<std::is_integral<T>::value> f(T t){
    //integral version
}
template<typename T>
std::enable_if_t<std::is_floating_point<T>::value> f(T t){
    //floating point version
}

Using a so called type sink idiom you can do pretty arbitrary tests on a type like checking if it has a member and if that member is of a certain type

//this goes in some header so you can use it everywhere
template<typename T>
struct TypeSink{
    using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;

//use case
template<typename T, typename=void>
struct HasBarOfTypeInt : std::false_type{};
template<typename T>
struct HasBarOfTypeInt<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>> :
    std::is_same<typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type,int>{};


struct S{
   int bar;
};
struct K{

};

template<typename T, typename = TypeSinkT<decltype(&T::bar)>>
void print(T){
    std::cout << "has bar" << std::endl;
}
void print(...){
    std::cout << "no bar" << std::endl;
}

int main(){
    print(S{});
    print(K{});
    std::cout << "bar is int: " << HasBarOfTypeInt<S>::value << std::endl;
}

Here is a live example: http://ideone.com/dHhyHE
I also recently wrote a whole section on SFINAE and tag dispatch in my blog (shameless plug but relevant) http://metaporky.blogspot.de/2014/08/part-7-static-dispatch-function.html

Note as of C++14 there is a std::void_t which is essentially the same as my TypeSink here.

眼中杀气 2024-07-30 06:59:11

Boost 的 enable_if 库为使用 SFINAE 提供了一个漂亮干净的界面。 我最喜欢的用法示例之一位于 Boost.Iterator< /a> 库。 SFINAE 用于启用迭代器类型转换。

Boost's enable_if library offers a nice clean interface for using SFINAE. One of my favorite usage examples is in the Boost.Iterator library. SFINAE is used to enable iterator type conversions.

那支青花 2024-07-30 06:59:11

在我看来,其他答案提供的示例比需要的更复杂。

这是 cppreference 中稍微容易理解的示例:

#include <iostream>
 
// this overload is always in the set of overloads
// ellipsis parameter has the lowest ranking for overload resolution
void test(...)
{
    std::cout << "Catch-all overload called\n";
}
 
// this overload is added to the set of overloads if
// C is a reference-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)(c.*f)(), void())
{
    std::cout << "Reference overload called\n";
}
 
// this overload is added to the set of overloads if
// C is a pointer-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)((c->*f)()), void())
{
    std::cout << "Pointer overload called\n";
}
 
struct X { void f() {} };
 
int main(){
  X x;
  test( x, &X::f);
  test(&x, &X::f);
  test(42, 1337);
}

输出:

Reference overload called
Pointer overload called
Catch-all overload called

如您所见,在第三次测试调用中,替换失败且没有错误。

Examples provided by other answers seems to me more complicated than needed.

Here is the slightly easier to understand example from cppreference :

#include <iostream>
 
// this overload is always in the set of overloads
// ellipsis parameter has the lowest ranking for overload resolution
void test(...)
{
    std::cout << "Catch-all overload called\n";
}
 
// this overload is added to the set of overloads if
// C is a reference-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)(c.*f)(), void())
{
    std::cout << "Reference overload called\n";
}
 
// this overload is added to the set of overloads if
// C is a pointer-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)((c->*f)()), void())
{
    std::cout << "Pointer overload called\n";
}
 
struct X { void f() {} };
 
int main(){
  X x;
  test( x, &X::f);
  test(&x, &X::f);
  test(42, 1337);
}

Output:

Reference overload called
Pointer overload called
Catch-all overload called

As you can see, in the third call of test, substitution fails without errors.

呆° 2024-07-30 06:59:11

这是另一个(最新的)SFINAE 示例,基于 Greg Rogers答案

template<typename T>
class IsClassT {
    template<typename C> static bool test(int C::*) {return true;}
    template<typename C> static bool test(...) {return false;}
public:
    static bool value;
};

template<typename T>
bool IsClassT<T>::value=IsClassT<T>::test<T>(0);

这样,您可以检查 value 的值来查看 T 是否是一个类:

int main(void) {
    std::cout << IsClassT<std::string>::value << std::endl; // true
    std::cout << IsClassT<int>::value << std::endl;         // false
    return 0;
}

Here's another (late) SFINAE example, based on Greg Rogers's answer:

template<typename T>
class IsClassT {
    template<typename C> static bool test(int C::*) {return true;}
    template<typename C> static bool test(...) {return false;}
public:
    static bool value;
};

template<typename T>
bool IsClassT<T>::value=IsClassT<T>::test<T>(0);

In this way, you can check the value's value to see whether T is a class or not:

int main(void) {
    std::cout << IsClassT<std::string>::value << std::endl; // true
    std::cout << IsClassT<int>::value << std::endl;         // false
    return 0;
}
小草泠泠 2024-07-30 06:59:11

这是 SFINAE 的一篇好文章: C++ 的 SFINAE 概念简介:编译时自省类成员的

总结如下:

/*
 The compiler will try this overload since it's less generic than the variadic.
 T will be replace by int which gives us void f(const int& t, int::iterator* b = nullptr);
 int doesn't have an iterator sub-type, but the compiler doesn't throw a bunch of errors.
 It simply tries the next overload. 
*/
template <typename T> void f(const T& t, typename T::iterator* it = nullptr) { }

// The sink-hole.
void f(...) { }

f(1); // Calls void f(...) { }

template<bool B, class T = void> // Default template version.
struct enable_if {}; // This struct doesn't define "type" and the substitution will fail if you try to access it.

template<class T> // A specialisation used if the expression is true. 
struct enable_if<true, T> { typedef T type; }; // This struct do have a "type" and won't fail on access.

template <class T> typename enable_if<hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return obj.serialize();
}

template <class T> typename enable_if<!hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return to_string(obj);
}

declval 是一个实用程序,它为您提供对无法轻松构造的类型的对象的“虚假引用”。 declval 对于我们的 SFINAE 构造来说非常方便。

struct Default {
    int foo() const {return 1;}
};

struct NonDefault {
    NonDefault(const NonDefault&) {}
    int foo() const {return 1;}
};

int main()
{
    decltype(Default().foo()) n1 = 1; // int n1
//  decltype(NonDefault().foo()) n2 = n1; // error: no default constructor
    decltype(std::declval<NonDefault>().foo()) n2 = n1; // int n2
    std::cout << "n2 = " << n2 << '\n';
}

Here is one good article of SFINAE: An introduction to C++'s SFINAE concept: compile-time introspection of a class member.

Summary it as following:

/*
 The compiler will try this overload since it's less generic than the variadic.
 T will be replace by int which gives us void f(const int& t, int::iterator* b = nullptr);
 int doesn't have an iterator sub-type, but the compiler doesn't throw a bunch of errors.
 It simply tries the next overload. 
*/
template <typename T> void f(const T& t, typename T::iterator* it = nullptr) { }

// The sink-hole.
void f(...) { }

f(1); // Calls void f(...) { }

template<bool B, class T = void> // Default template version.
struct enable_if {}; // This struct doesn't define "type" and the substitution will fail if you try to access it.

template<class T> // A specialisation used if the expression is true. 
struct enable_if<true, T> { typedef T type; }; // This struct do have a "type" and won't fail on access.

template <class T> typename enable_if<hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return obj.serialize();
}

template <class T> typename enable_if<!hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return to_string(obj);
}

declval is an utility that gives you a "fake reference" to an object of a type that couldn't be easily construct. declval is really handy for our SFINAE constructions.

struct Default {
    int foo() const {return 1;}
};

struct NonDefault {
    NonDefault(const NonDefault&) {}
    int foo() const {return 1;}
};

int main()
{
    decltype(Default().foo()) n1 = 1; // int n1
//  decltype(NonDefault().foo()) n2 = n1; // error: no default constructor
    decltype(std::declval<NonDefault>().foo()) n2 = n1; // int n2
    std::cout << "n2 = " << n2 << '\n';
}
微暖i 2024-07-30 06:59:11

以下代码使用 SFINAE 让编译器根据类型是否具有特定方法来选择重载:

    #include <iostream>
    
    template<typename T>
    void do_something(const T& value, decltype(value.get_int()) = 0) {
        std::cout << "Int: " <<  value.get_int() << std::endl;
    }
    
    template<typename T>
    void do_something(const T& value, decltype(value.get_float()) = 0) {
        std::cout << "Float: " << value.get_float() << std::endl;
    }
    
    
    struct FloatItem {
        float get_float() const {
            return 1.0f;
        }
    };
    
    struct IntItem {
        int get_int() const {
            return -1;
        }
    };
    
    struct UniversalItem : public IntItem, public FloatItem {};
    
    int main() {
        do_something(FloatItem{});
        do_something(IntItem{});
        // the following fails because template substitution
        // leads to ambiguity 
        // do_something(UniversalItem{});
        return 0;
    }

输出:

Float: 1
Int: -1

The following code uses SFINAE to let compiler select an overload based on whether a type has certain method or not:

    #include <iostream>
    
    template<typename T>
    void do_something(const T& value, decltype(value.get_int()) = 0) {
        std::cout << "Int: " <<  value.get_int() << std::endl;
    }
    
    template<typename T>
    void do_something(const T& value, decltype(value.get_float()) = 0) {
        std::cout << "Float: " << value.get_float() << std::endl;
    }
    
    
    struct FloatItem {
        float get_float() const {
            return 1.0f;
        }
    };
    
    struct IntItem {
        int get_int() const {
            return -1;
        }
    };
    
    struct UniversalItem : public IntItem, public FloatItem {};
    
    int main() {
        do_something(FloatItem{});
        do_something(IntItem{});
        // the following fails because template substitution
        // leads to ambiguity 
        // do_something(UniversalItem{});
        return 0;
    }

Output:

Float: 1
Int: -1
◇流星雨 2024-07-30 06:59:11

C++17 可能会提供一种查询功能的通用方法。 请参阅 N4502 了解详细信息,但作为一个独立的示例,请考虑以下内容。

这部分是常量部分,将其放在标题中。

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

以下示例取自 N4502 的支持显示了用法:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

与其他实现相比,这个实现相当简单:一组精简的工具(void_t检测)就足够了。 此外,还报告了(参见N4502),它比以前的方法明显更高效(编译时间和编译器内存消耗)。

这是一个 实时示例,其中包括 GCC pre 的可移植性调整5.1.

C++17 will probably provide a generic means to query for features. See N4502 for details, but as a self-contained example consider the following.

This part is the constant part, put it in a header.

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

The following example, taken from N4502, shows the usage:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

Compared to the other implementations, this one is fairly simple: a reduced set of tools (void_t and detect) suffices. Besides, it was reported (see N4502) that it is measurably more efficient (compile-time and compiler memory consumption) than previous approaches.

Here is a live example, which includes portability tweaks for GCC pre 5.1.

老子叫无熙 2024-07-30 06:59:11

在这里,我使用模板函数重载(不是直接 SFINAE)来确定指针是函数还是成员类指针:(是否可以修复 iostream cout/cerr 成员函数指针打印为 1 或 true 的问题?)

https://godbolt.org/z/c2NmzR

#include<iostream>

template<typename Return, typename... Args>
constexpr bool is_function_pointer(Return(*pointer)(Args...)) {
    return true;
}

template<typename Return, typename ClassType, typename... Args>
constexpr bool is_function_pointer(Return(ClassType::*pointer)(Args...)) {
    return true;
}

template<typename... Args>
constexpr bool is_function_pointer(Args...) {
    return false;
}

struct test_debugger { void var() {} };
void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}

int main(void) {
    int* var;

    std::cout << std::boolalpha;
    std::cout << "0. " << is_function_pointer(var) << std::endl;
    std::cout << "1. " << is_function_pointer(fun_void_void) << std::endl;
    std::cout << "2. " << is_function_pointer(fun_void_double) << std::endl;
    std::cout << "3. " << is_function_pointer(fun_double_double) << std::endl;
    std::cout << "4. " << is_function_pointer(&test_debugger::var) << std::endl;
    return 0;
}

打印

0. false
1. true
2. true
3. true
4. true

正如代码所示,它可以 (取决于编译器“好”将)生成对函数的运行时调用,该函数将返回 true 或 false。 如果您想强制 is_function_pointer(var) 计算编译类型(运行时不执行函数调用),您可以使用 constexpr 变量技巧

constexpr bool ispointer = is_function_pointer(var);
std::cout << "ispointer " << ispointer << std::endl;

: C++ 标准,所有 constexpr 变量都保证在编译时进行计算 (在编译时计算 C 字符串的长度。这真的是 constexpr 吗?)。

Here, I am using template function overloading (not directly SFINAE) to determine whether a pointer is a function or member class pointer: (Is possible to fix the iostream cout/cerr member function pointers being printed as 1 or true?)

https://godbolt.org/z/c2NmzR

#include<iostream>

template<typename Return, typename... Args>
constexpr bool is_function_pointer(Return(*pointer)(Args...)) {
    return true;
}

template<typename Return, typename ClassType, typename... Args>
constexpr bool is_function_pointer(Return(ClassType::*pointer)(Args...)) {
    return true;
}

template<typename... Args>
constexpr bool is_function_pointer(Args...) {
    return false;
}

struct test_debugger { void var() {} };
void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}

int main(void) {
    int* var;

    std::cout << std::boolalpha;
    std::cout << "0. " << is_function_pointer(var) << std::endl;
    std::cout << "1. " << is_function_pointer(fun_void_void) << std::endl;
    std::cout << "2. " << is_function_pointer(fun_void_double) << std::endl;
    std::cout << "3. " << is_function_pointer(fun_double_double) << std::endl;
    std::cout << "4. " << is_function_pointer(&test_debugger::var) << std::endl;
    return 0;
}

Prints

0. false
1. true
2. true
3. true
4. true

As the code is, it could (depending on the compiler "good" will) generate a run time call to a function which will return true or false. If you would like to force the is_function_pointer(var) to evaluate at compile type (no function calls performed at run time), you can use the constexpr variable trick:

constexpr bool ispointer = is_function_pointer(var);
std::cout << "ispointer " << ispointer << std::endl;

By the C++ standard, all constexpr variables are guaranteed to be evaluated at compile time (Computing length of a C string at compile time. Is this really a constexpr?).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文