771 words
4 minutes
SFINAE
2025-03-11
No Tags

SFINAEsubstitution failure is not an error 的缩写

  • 有一个单词的字符串需要匹配一些条件
    • 如果没有匹配上其中某一个条件,称为failure
    • 如果这个匹配的不是字符串,则称发生了error

注意区分 errorfailure

将模板形参替换为实参的过程就称为substitute

举个例子,需要

struct ICounter {
    virtual void increase() = 0;
    virtual ~ICounter() {}
};

struct Counter : public ICounter {
    void increase() override {}
};

template <typename T>
void inc_counter(T& counter_obj) {
    counter_obj.increase();
}

template <typename T>
void inc_counter(T& int_type_counter) {
    ++int_type_counter;
}

void do_sth() {
    Counter counter_obj;
    uint32_t cnt;

    inc_counter(counter_obj);
    inc_counter(cnt);
}

int main() {
    do_sth();
}

这样写是不可以的,因为函数定义完全相同,会造成redefinition

可以通过 <type_traits> 中定义的方法来进行编译期的类型检查

#include <type_traits>

struct ICounter {
    virtual void increase() = 0;
    virtual ~ICounter() = default;
};

struct Counter : public ICounter {
    void increase() override {}
};

template <typename T>
void inc_counter(T& counter_obj,
                 typename std::enable_if<std::is_base_of_v<ICounter, T> >::type* = nullptr) {
    counter_obj.increase();
}

template <typename T>
void inc_counter(T& int_type_counter,
                 typename std::enable_if<std::is_integral_v<T> >::type* = nullptr) {
    ++int_type_counter;
}

void do_sth() {
    Counter counter_obj;
    uint32_t cnt;

    inc_counter(counter_obj);
    inc_counter(cnt);
}

int main() {
    do_sth();
}

解释一下这个函数定义

template <typename T>
void inc_counter(T& counter_obj, typename std::enable_if<std::is_base_of<T, ICounter>::value>::type* = nullptr);
  1. template <typename T>:这是一个函数模板的定义,表示函数将接受一个模板类型参数 T。这意味着函数可以在调用时用不同的类型进行实例化。
  2. void inc_counter(T& counter_obj, ...):这是函数的声明,它定义了一个名为 inc_counter 的函数,它接受两个参数。第一个参数 counter_obj 是一个引用,它表示计数器对象,将用于执行自增操作。第二个参数是一个使用模板元编程技巧的默认参数(默认值为 nullptr),稍后我们将详细解释。
  3. typename std::enable_if<std::is_base_of<T, ICounter>::value>::type* = nullptr 这是一个奇妙的默认参数,我们分开来看:
    • std::is_base_of<T, ICounter>::value
      • 这部分使用类型特性和元编程,检查类型 T 是否是 ICounter 类型的派生类(或者说,是否 ICounterT 的基类)。如果是,std::is_base_of<T, ICounter>::value 为true,否则为false。
    • typename std::enable_if<...>::type* = nullptr
      • std::enable_if 是一个元编程工具,它根据条件启用或禁用函数模板。如果 std::is_base_of<T, ICounter>::value 为真(true),那么 std::enable_if 的条件成立,允许函数被实例化。如果条件不成立,那么 std::enable_if 将导致函数不可实例化。
    • = nullptr
      • 这是默认参数的一部分,表示如果条件成立(即 TICounter 的派生类),则默认参数为 nullptr。如果条件不成立,这个默认参数没有实际影响,因为它永远不会被使用。

因此,这个函数模板 inc_counter 允许你传递一个计数器对象作为参数,并且只有当该对象的类型是 ICounter 或其派生类时,函数才会被实例化。

也就是说,这个函数只能用于实现对 ICounter 类型对象的自增操作,以确保类型安全。如果你尝试传递不是 ICounter 类型的对象,编译器将会产生错误,因为函数无法被实例化。这有助于在编译阶段捕获潜在的类型错误。

SFINAE
https://fuwari.vercel.app/posts/sfinae/
Author
time
Published at
2025-03-11