C++朝泛型也就是模板化编程的方向越走越远了。SFIANE应该是从C++模板中早就存在的一个概念,是Substitution Failure Is Not An Error的缩写。C++11之后对SFINAE的使用越发发扬光大了。对SFINAE简单的不那么准确的解释:如果几个模板共同定义一个类(或者函数),如果其中某些实例化失败,但是其他的有的能实例化成功的,那么实例化失败的模板就不能算错误。

一个例子

首先来看一个来自Wikipedia SFINAE的例子(经过一些修改):

// 模板主版本
template <typename T, typename = void>
struct has_size_type : std::false_type {};

// 特化版本
template <typename T>
struct has_size_type<T, std::void_t<typename T::size_type>> : std::true_type {};

上面的has_size_type类模板用来检查模板参数类型T是否定义了子类型size_type。对于某个T,编译器会优先选择特化版本,如果特化版本实例化失败(因为不含size_type),编译器就会退回选择主版本。特化版本从std::true_type继承,带有一个值为true的value成员;而主版本从std::false_type继承,其成员value值为false。

下面是一个实例程序:

// sfi0.cpp
#include <iostream>
#include <string>
using namespace std;

// ... has_size_type定义

int main()
{
  cout << has_size_type<string>::value << endl;
  return 0;
}

在Visual Studio 2019的命令行上使用cl /EHsc /std:c++17 sfi0.cpp来编译,执行结果为1

如何检测一个类型是否拥有 某种方法

#include <iostream>
#include <vector>
#include <type_traits>
using namespace std;

template<typename T>
struct has_data_size
{
  template<typename Y>
    static constexpr bool checkMethods(decltype(std::declval<T>().data()) returnTypeOfData = nullptr,
                                       decltype(std::declval<T>().size()) returnTypeOfSize = 0)
    {
      if constexpr (std::is_convertible_v<decltype(returnTypeOfData), const char*> &&
                    std::is_convertible_v<decltype(returnTypeOfSize), int>) {
        return true;
      } else {
        return false;
      }
    }

  template<typename Y>
    static constexpr bool checkMethods(...)
    {
      return false;
    }

  static constexpr bool value = checkMethods<T>(nullptr, 0);
};


struct Foo {};

struct Bar {
  char* data() {
    return nullptr;
  }
};

int main() {
  string str = "hello";
  vector<char> vc;
  cout << "string: " << has_data_size<decltype(str)>::value << endl;
  cout << "vector<char>: " << has_data_size<decltype(vc)>::value << endl;
  cout << "Foo: " << has_data_size<Foo>::value << endl;
  cout << "Bar: " << has_data_size<Bar>::value << endl;
  cout << "string_view: " << has_data_size<std::string_view>::value << endl;
  return 0;
}

上面例子中的has_data_size模板用来检测一个类型是否有const char* data()方法,以及size_t size()方法,上面的例子使用到了几种技术:

  • 嵌套模板,checkMethods是一个嵌套的模板函数
  • checkMethods第二个版本可以接受任意参数,其参数表为(...),但是这个在模板版本选择的时候次序较低
  • if constexpr是C++17引入的,可以在编译阶段根据静态条件挑选出有用的执行路径,删除不必要的代码

上面例子的运行结果为:

string: 1
vector<char>: 1
Foo: 0
Bar: 0
string_view: 1

(完)