C++模版SFIANE应用踩的一个小坑

一天一个C++大佬同事,突然截图过来一段代码:这写的啥呀,啰里吧嗦的,这个构造函数模板参数T1感觉是多余的呀

cpp 复制代码
template<class T>
class TestClass
{
public:
    TestClass(){}

    //函数1
    template<class T1 = T, std::enable_if_t<std::is_same_v<int, T1>,bool> = true>
    TestClass()
    {
         //balabala
    }

    //函数2
    template<class T1 = T, std::enable_if_t<!std::is_same_v<int, T1>, bool> = true>
    TestClass()
    {
        //balabala
    }

    ......
};

int main() {
    TestClass<double> test;
} 

我说,这可能是那个不懂C++的人写出来的吧,看,写成下面的形式多简洁阿~

cpp 复制代码
template<class T>
class TestClass
{
public:
    TestClass(){}

    //函数3
    template<std::enable_if_t<std::is_same_v<int, T>, bool> = true>
    TestClass()
    {
    }

    //函数4
    template<std::enable_if_t<!std::is_same_v<int, T>, bool> = true>
    TestClass()
    {

    }
};

int main() {
    TestClass<double> test;
} 

于是就被打脸了,直接给我整出了一堆编译错误:

给我整一脸懵逼,上面函数1和2好好的,咋改成3和4就不行了呢? 看来,真的不能随便改别人代码。于是去网上查了查, 编译器错误 C2893 | Microsoft Learn

cpp 复制代码
// C2893.cpp
//以下示例生成 C2893。
// compile with: /c /EHsc
#include<map>
using namespace std;
class MyClass {};

template<class T>
inline typename T::data_type
// try the following line instead
// inline typename  T::mapped_type
f(T const& p1, MyClass const& p2);

template<class T>
void bar(T const& p1) {
    MyClass r;
    f(p1,r);   // C2893
}

int main() {
   map<int,int> m;
   bar(m);
}

发生 C2893 的原因是,f 的模板参数 T 被推断为 std::map<int,int>,但 std::map<int,int> 没有成员 data_type(无法使用 T = std::map<int,int> 实例化 T::data_type)。

看来编译错误产生的原因是 std::enable_if_t<std::is_same_v<int, double>, bool>即 std::enable_if_t<false, bool>是未定义类型导致的。可是函数1和函数3到底有什么区别呢? 绞尽脑汁,百思不得其解,最痛通过找不同的方式,终于悟了:这两个唯一的差别就是,T是类模板参数,T1是类构造函数模板参数。后面回顾了一下Sfiane相关的知识,终于找到问题的根本原因:

SFINAE 原理

SFINAE(Substitution Failure Is Not An Error)是 C++ 模板机制的一部分,当模板参数替换导致的模板不合法时,模板不会引发编译错误,而是会被编译器静默排除。然而,SFINAE 只适用于函数模板参数替换 阶段,而不适用于非模板参数替换阶段的错误。

我们先来看一下函数3

cpp 复制代码
template<std::enable_if_t<std::is_same_v<int, T>, bool> = true>
TestClass()
{
}

在这个函数模板中:

std::enable_if_t<std::is_same_v<int, T>, bool> = true 是模板参数的默认参数。编译器会在函数模板实例化时尝试解析这部分默认参数。

  • std::enable_if_t<std::is_same_v<int, T>, bool> 依赖于 T,它是类模板 TestClass<T> 的参数。在 类模板实例化时,编译器已经需要评估这个表达式来确定默认值是否有效。
  • 如果 T 不等于 int(例如 TestClass<double>),std::is_same_v<int, T> 变成 false,这时 std::enable_if_t<false, bool> 试图生成一个无效类型,这会导致编译错误,而不是被 SFINAE 排除。

SFINAE 只作用于模板参数替换期间产生的错误,而这个默认参数的实例化所依赖的类型T属于类模板参数,不属于该函数模板参数替换阶段。这意味着:

  • 编译器在遇到默认参数时需要立即评估它,而不是等到参数替换期间再进行评估。
  • 因此,如果默认参数表达式在定义时无效(比如 std::enable_if_t<false, bool>),编译器会报错,而不是通过 SFINAE 排除它。

我们再来看一下函数1

cpp 复制代码
template<class T1 = T, std::enable_if_t<std::is_same_v<int, T1>, bool> = true>
TestClass(int i)
{
}

在函数1中,T1 是一个新的模板参数,并且默认值为 T。因此,SFINAE 是在模板参数 T1 替换阶段应用的:

  • 如果 T1 不满足 std::is_same_v<int, T1>,则该模板的实例化会失败,SFINAE 会将此构造函数排除在重载集合之外。

这里的关键在于:

  • T1 是函数模板的一个参数,所以 std::enable_if_t 检查是在模板参数替换阶段发生的。
  • 如果替换导致无效,则会被 SFINAE 静默排除,不会报编译错误。

这个例子中通过引入额外的模板参数(如 T1),你可以推迟 enable_if 的检查,使其在模板参数替换阶段才进行,从而避免编译错误。

相关推荐
数智化转型推荐官8 分钟前
2026统一身份管理系统五大发展趋势解读
java·运维·微服务
看菜鸡互12 分钟前
探索用 SlideML 让大模型生成 PPT 的实验方法
java·运维
jimy12 小时前
C语言模拟对象、方法:“函数指针+结构体“复用函数指针指向的函数体
c语言·开发语言
程序员张32 小时前
SpringBoot集成BCrypt密码加密库
java·spring boot·后端
闲猫2 小时前
Spring AI Agentic 模式(第1部分):Agent Skills——模块化、可复用的能力
java·人工智能·spring
源图客2 小时前
云途物流API开发-鉴权认证(Java)
java·网络·python
天外天-亮2 小时前
HBuilder X 使用 uview-plus 方式
开发语言·javascript·hbuilder x·uview-plus
liguojun20252 小时前
篮球馆自动计时收费系统:从规则配置到自动结算的全流程拆解
java·大数据·运维·人工智能·物联网·1024程序员节
木木子223 小时前
[特殊字符] 音乐播放器——状态驱动的多媒体控制
android·开发语言·华为·php·harmonyos
952363 小时前
RabbitMQ-基础操作
java·spring boot·分布式·后端·spring·rabbitmq