error c2672: “sort”: 未找到匹配的重载函数_Design Patterns With C++(七)SFINAE与重载解析管理(中)...

本文介绍了C++中的SFINAE原则,即替换失败不是错误,以及如何利用SFINAE控制函数重载。通过示例解释了如何使用SFINAE编写复杂程序,包括基础和高级应用,如通过自定义test_sort方法实现排序的SFINAE策略。

63d95444f7b7eab30a76fd55d9e6367f.png

目录:

trick:Hands-On Design Patterns With C++(零)前言​zhuanlan.zhihu.com
115fa7ff4026edb9c3c66558a3b28a3d.png

SFINAE与重载解析管理(上):

trick:Design Patterns With C++(七)SFINAE与重载解析管理(上)​zhuanlan.zhihu.com
115fa7ff4026edb9c3c66558a3b28a3d.png

本章代码:

https://github.com/PacktPublishing/Hands-On-Design-Patterns-with-CPP/tree/master/Chapter07​github.com

SFINAE与重载解析管理(中)

SFINAE: Substitution Failure Is Not An Error 匹配失败不是错误

我们本章将讨论以下几个话题:

  1. 什么是函数重载与重载解析?(见上篇)
  2. 什么是类型演绎和类型匹配?(见上篇)
  3. SFINAE是什么?为什么它对C++来说十分重要?(本文)
  4. 如何使用SFINAE编写复杂的程序?(本文及下一篇文章)

SFINAE是什么?SFINAE 替换失败不是错误

此规则的含义是:类型推断引起的替换失败不会引起整个程序发生错误。此规则对于C++模板方法十分重要,没有SFINAE,就无法编写出很多有效的程序。

考虑如下模板重载,一个是普通指针,一个是成员指针:

template <typename T>
void f(T* i) { // 普通指针
    std::cout << "f(T*)" << std::endl;
}

template <typename T>
void f(int T::* p) { // 成员指针
    std::cout << "f(T::*)" << std::endl;
}

struct A {
    int i;
    int f() { return 0; }
};

int main() {
    A a;
    f(&a.i); // f(T*)
    f(&A::i); // f(T::*)
}

考虑如下代码:

int i;
f(&i); // f(T*)

上述代码与f(T*)重载方法完全匹配,而与f(T::*)无效,编译器遇到f(T::*)方法发现会导致语法错误,就会静默忽略此重载方法。

SFINAE对于无效类型也没有限制,比如不存在的类成员引用。替换失败有很多方式:

template <size_t N>
void f(char(*)[N % 2] = NULL) {
    std::cout << "N=" << N << " is odd" << std::endl;
}

template <size_t N>
void f(char(*)[1 - N % 2] = NULL) {
    std::cout << "N=" << N << " is even" << std::endl;
}

int main() {
    f<5>(); // N=5 is odd
    f<8>(); // N=8 is even
}

上例中,f(char(*)[N % 2] = NULL)的版本适用于奇数(5 % 2 == 1),f(char(*)[1 - N % 2] = NULL)适用于偶数版本(1 - 8 % 2 = 1)。在调用f重载方法时,两个重载中其中一个会被静默忽略,否则会引起歧义失败。

template<typename T, size_t N = T::N>
void f(T t, char(*)[N % 2] = NULL) { 
    std::cout << "N=" << N << " is odd" << std::endl; 
}

template<typename T, size_t N = T::N>
void f(T t, char(*)[1 - N % 2] = NULL) { 
    std::cout << "N=" << N << " is even" << std::endl; 
}

struct A {
    enum {
        N = 5
    };
};

struct B {
    enum {
        N = 8
    };
};

int main() {
    A a;
    B b;
    f(a); // N=5 is odd
    f(b); // N=8 is even
}

f(a)调用后,编译器查找模板,将T替换为A类,然后继续看A::N是否满足条件,忽略替换失败的模板,选择正确的模板版本进行调用,f(b)同理。

注意:由于模板实例化期间发生的语法错误,SFINAE是无法进行保护的

template<typename T>
void f(T) { std::cout << "f(T) " << sizeof(T::i) << std::endl; }

void f(...) { std::cout << "f(...)" << std::endl; }

int main() {
    f(0); // 编译失败
}

上述代码中,模板中并没有指明要求T有i成员变量,所以当调用f(0)时,编译器认为符合模板方法f(T),然而方法中需要T::i成员变量存在,这时候编译失败。编译器这时候不会再选择其它合法方法如f(...)。最终报错如下:

error: ‘i’ is not a member of ‘int’
void f(T) { std::cout << "f(T) " << sizeof(T::i) << std::endl; }
                                                                            ~^~~~~

一旦模板被实例化,任何语法错误都不会被忽略,这就会造成失败!

控制重载(如何使用SFINAE编写复杂的程序)

SFINAE使C++实现了狭义的模板函数定义,程序员可以通过替换失败来控制重载方法。下面让我们开始深入SFINAE,通过SFINAE来消除多余的重载方法,实现对重载的控制。

下面将从Basic,Advanced,Ultimate三个阶段深入SFINAE。本章内容依赖于C++11甚至C++14才有的特性进行讲解。(本篇讲解Basic与Advanced,下篇讲解Ultimate)

(一)Basic SFINAE

让我们从一个简单的例子开始,C++有很多类型,int、double等内置类型,自己声明的类,还有指针引用等类型。现在我们要找出哪些类型属于类,哪些类型是基本类型,所以我们要找出两者可能的区别,最明显的区别就是类有成员指针,而内置基本类型没有,所以有如下例子:

template<typename T>
void f(int T::*) { std::cout << "T is a class" << std::endl; }

template<typename T>
void f(...) { std::cout << "T is not a class" << std::endl; }

struct A {
};

int main() {
    f<int>(0); // T is not a class
    f<A>(0); // T is a class
}

f<int>(0)对于f(int T::*)会发生替换失败,失败不是错误,忽略;另一个重载f(...)匹配,所以f<int>(0)调用了f(...)。对于f<A>,f(int T::*)替换成功。

当然,我们应该通过一个is_class类去判断是否是一个类还是内置基本类型,大概的样子如下:

template<typename T> ??? test(int T::*);

template<typename T> ??? test(...);

struct is_class {
    ...
    static constexpr bool value = ???; // 如果是一个类,此值应为true
};

bool value是一个标志,如果推断出是类A,那么test返回值为1,否则为0:

template<typename T>
class is_class {
    template<typename C>
    static char test(int C::*); // C是个类调用此方法

    template<typename C>
    static int test(...);      // 非类调用此方法

public:
    static constexpr bool value = sizeof(test<T>(NULL)) == 1;
};

struct A {
};

int main() {
    std::cout << is_class<int>::value << std::endl; // 0。非类
    std::cout << is_class<A>::value << std::endl; // 1。是类
}

sizeof(char) == 1,sizeof(int) == 4,所以是类调用返回类型为char的值,非类调用返回类型为int的值,所以bool value可以通过sizeof test函数的返回值来判断是类还是非类。

如上就是基本的SFINAE去匹配模板,实现简单的分类功能。

(二)Advanced SFINAE

我们考虑一个简单的场景,对于模板中的类型T,我们要对其中的元素做排序,有的容器提供了T::sort()方法,有些则没有,没有成员变量T::sort()方法的容器排序需要使用标准库中的std::sort()进行排序。

简单一想,解决方案如下:

template <typename T> void best_sort(T& x, bool use_member_sort) {
    if (use_member_sort) x.sort();
    else std::sort(x.begin(), x.end());
}

上述方法传入是否使用T自身的sort方法的bool值,这样做当然不好,你要了解当前使用容器是否有sort方法,很麻烦而且容易出错(需要在编译期才能确定是否OK,而且不利于扩展)。

同样的错误实例如下:

class Base {
public:
    Base() : i_() {}
    virtual void increment(long v) { i_ += v; }
private:
    long i_;
};

template<typename T>
class Derived : public T {
public:
    Derived() : T(), j_() {}
    Derived(long i, long j) : T(i), j_(j) {}
    void increment(long v) { // Base有此方法
        j_ += v;
        T::increment(v);
    }

    void multiply(long v) { // Base无此方法
        j_ *= v;
        T::multiply(v); // T为Base则会调用Base::multiply,但是父类没有此方法
    }

    long getJ() {
        return j_;
    }

private:
    long j_;
};

int main() {
    Derived<Base> d;
    d.increment(5);
    std::cout << d.getJ() << std::endl;
}

注意,上面的代码通过编译,而且运行正常,因为没有调用Derived的multiply方法,所以编译器不会报错,如果调用了multiply方法,编译器会在编译阶段报错。这样的情况我们也是要用SFINAE去解决的。

下面我们开始用SFINAE的方法去实现开头说到的排序!

通过自定义test_sort方法实现排序SFINAE

基本的模型如下:

template <typename T>
struct fast_sort_helper<???> { // ???为准入条件
    ...
}

充实模型之后:

template <typename T>
struct fast_sort_helper<T, sizeof(have_sort)> { // 准入条件sizeof(have_sort),使用T::sort()
    static void fast_sort(T& x) {
        std::cout << "Sorting with T::sort" << std::endl;
        x.sort();
    }
};

template <typename T>
struct fast_sort_helper<T, sizeof(have_range)> { // 准入条件sizeof(have_range),使用std::sort()
    static void fast_sort(T& x) {
        std::cout << "Sorting with std::sort" << std::endl;
        std::sort(x.begin(), x.end());
    }
};

template <typename T>
struct fast_sort_helper<T, sizeof(have_nothing)> { // 准入条件sizeof(have_nothing)
    static void fast_sort(T& x) {
        std::cout << "No sort available" << std::endl;
    }
};

template <typename T>
void fast_sort(T& x) {
    fast_sort_helper<T, sizeof(test_sort<T>(nullptr, nullptr))>::fast_sort(x); // 使用test_sort去区分
}

我们仍然用Basic SFINAE里面的sizeof作为区分方法:

struct have_sort { char c; }; // sizeof(have_sort) == 1
struct have_range { char c; have_sort c1; }; // sizeof(have_range) == 2
struct have_nothing { char c; have_range c1; }; // sizeof(have_nothing) == 3

我们需要写一个方法去执行区分操作,这里我们起名为test_sort:

template<typename T>
have_sort test_sort(decltype(&T::sort), decltype(&T::sort)); // 是否有成员方法T::sort

template<typename T>
have_range test_sort(decltype(&T::begin), decltype(&T::end)); // 是否有T::begin和T::end成员方法

template<typename T>
have_nothing test_sort(...); // 变长参数模板,默认走这里

decltype是c++中用于做类型推导的关键字,在泛型编程中经常使用,这里用来查看T中是否有某些方法。这里不再像Basic那样只有两种情况(Basic一个bool值就能选出一个特定模板的情况)。

此方式现在有一个致命的问题,就是如果某个类既有T::sort方法,也有T::begin和T::end方法,程序就会出现歧义,编译不过!!!

error: call of overloaded ‘test_sort<B>(std::nullptr_t, std::nullptr_t)’ is ambiguous

如何解决这个问题呢,我们可以通过制定条件细节来筛选方法,这次我们构造yes和no两种条件来表示是否支持某种特性:

struct yes { char c; };
struct no { char c; yes c1; };
template <typename T> yes test_have_sort(decltype(static_cast<void (T::*)()>(&T::sort))); // 是否有自己的sort
template <typename T> no test_have_sort(...);

template <typename T> yes test_have_range(decltype(static_cast<typename T::iterator (T::*)()>(&T::begin)), decltype(static_cast<typename T::iterator (T::*)()>(&T::end))); // 是否有begin和end方法
template <typename T> no test_have_range(...);

template <typename T, bool have_sort, bool have_range> struct fast_sort_helper;

注意上面的fast_sort_helper中相比于之前的struct fast_sort_helper变成了struct fast_sort_helper,分别表示test_have_sort和test_have_range两种条件。

最终,fast_sort_helper几种实现如下:

template<typename T>
struct fast_sort_helper<T, true, true> { // 既有成员变量sort也有begin、end走T::sort
    static void fast_sort(T &x) {
        std::cout << "Sorting with T::sort, ignoring std::sort" << std::endl;
        x.sort();
    }
};

template<typename T>
struct fast_sort_helper<T, true, false> { // 有成员变量sort,无begin、end走T::sort
    static void fast_sort(T &x) {
        std::cout << "Sorting with T::sort" << std::endl;
        x.sort();
    }
};

template<typename T>
struct fast_sort_helper<T, false, true> { // 无成员变量sort,同时也无begin、end走std::sort
    static void fast_sort(T &x) {
        std::cout << "Sorting with std::sort" << std::endl;
        std::sort(x.begin(), x.end());
    }
};

template<typename T>
struct fast_sort_helper<T, false, false> { // 两个都无,无法排序
    static void fast_sort(T &x) {
        std::cout << "No sort available" << std::endl;
    }
};

template<typename T>
void fast_sort(T &x) {
    fast_sort_helper<T,
            sizeof(test_have_sort<T>(nullptr)) == sizeof(yes), // 是否有成员函数sort
            sizeof(test_have_range<T>(nullptr, nullptr)) == sizeof(yes)>::fast_sort(x); // 是否有begin、end
}

以上就是测试的全部代码,查看完整代码请看文末Advanced SFINAE最终完整代码

到了这里,似乎还是有些不完美的地方。在筛选重载函数的过程中,我们编写的方案是否过于具体?再或者函数带有返回值时如何处理(返回值类型我们无法确定)?对于SFINAE的使用我们需要有其他考虑吗?下一篇文章我们再继续讨论。

PS:下一篇文章是SFINAE最后一篇文章,继续讨论Advanced SFINAE以及讲解最后的Ultimate SFINAE技巧。

相关代码

Advanced SFINAE最终完整代码:

#include <iostream>
#include <algorithm>
#include <vector>
#include <list>

struct yes { char c; };
struct no { char c; yes c1; };

// 测试条件,yes或no,是否有成员函数sort以及是否有begin、end方法
template<typename T>
yes test_have_sort(decltype(static_cast<void (T::*)()>(&T::sort)));

template<typename T>
no test_have_sort(...);

template<typename T>
yes test_have_range(decltype(static_cast<typename T::iterator (T::*)()>(&T::begin)),
                    decltype(static_cast<typename T::iterator (T::*)()>(&T::end)));

template<typename T>
no test_have_range(...);

// fast_sort_helper定义,两个bool代表上面的条件
template<typename T, bool have_sort, bool have_range>
struct fast_sort_helper;

// 两个条件一共四种组合,明确条件选出唯一的模板
template<typename T>
struct fast_sort_helper<T, true, true> {
    static void fast_sort(T &x) {
        std::cout << "Sorting with T::sort, ignoring std::sort" << std::endl;
        x.sort();
    }
};

template<typename T>
struct fast_sort_helper<T, true, false> {
    static void fast_sort(T &x) {
        std::cout << "Sorting with T::sort" << std::endl;
        x.sort();
    }
};

template<typename T>
struct fast_sort_helper<T, false, true> {
    static void fast_sort(T &x) {
        std::cout << "Sorting with std::sort" << std::endl;
        std::sort(x.begin(), x.end());
    }
};

template<typename T>
struct fast_sort_helper<T, false, false> {
    static void fast_sort(T &x) {
        std::cout << "No sort available" << std::endl;
    }
};

// fast_sort入口
template<typename T>
void fast_sort(T &x) {
    fast_sort_helper<T,
            sizeof(test_have_sort<T>(nullptr)) == sizeof(yes),
            sizeof(test_have_range<T>(nullptr, nullptr)) == sizeof(yes)>::fast_sort(x);
}

// 几种用于测试的类
class A {
public:
    void sort() {}
};

class A1 {
public:
    void sort() {}
    void sort(int i) {}
};

class B {
public:
    typedef int *iterator;
    iterator begin() { return i; }
    iterator end() { return i + 10; }
    int i[10];
};

class AB {
public:
    typedef int *iterator;
    void sort() {}
    iterator begin() { return i; }
    iterator end() { return i + 10; }
    int i[10];
};

class B1 {
public:
    typedef int *iterator;
    typedef const int *const_iterator;
    iterator begin() { return i; }
    const_iterator begin() const { return i; }
    iterator end() { return i + 10; }
    const_iterator end() const { return i + 10; }
    int i[10];
};

class C {
public:
    void f() {}
};

int main() {
    A a;
    fast_sort(a); // Sorting with T::sort
    B b;
    fast_sort(b); // Sorting with std::sort
    C c;
    fast_sort(c); // No sort available
    A1 a1;
    fast_sort(a1); // Sorting with T::sort
    B1 b1;
    fast_sort(b1); // Sorting with std::sort
    std::vector<int> v;
    fast_sort(v); // Sorting with std::sort
    AB ab;
    fast_sort(ab); // Sorting with T::sort, ignoring std::sort
    std::list<int> l;
    fast_sort(l); // Sorting with T::sort, ignoring std::sort
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值