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 的检查,使其在模板参数替换阶段才进行,从而避免编译错误。

相关推荐
非优秀程序员3 小时前
第04章 邮箱大战,163 与 QQ 的暗战|《互联网产品恩仇录》
java·vue.js·架构
知已难寻4 小时前
Docker 入门:容器技术与 Docker 基础
java·运维·服务器·docker·云原生·容器
沐晓时光4 小时前
C语言入门,内存函数知识讲解
c语言·开发语言
文丑颜不良啊6 小时前
LeetCode2481~2500题目解析
java·leetcode
Flynn Liang7 小时前
同一需求三套写法:用 Spring AI、LangChain4j、AgentScope Java 各实现一个带工具调用的 RAG 助手(实战对比)
java·人工智能·大模型·架构设计·spring ai·langchain4j·agentscope
聪明的笨猪猪8 小时前
实际工程问题:AI 生成 Spring Cache(@Cacheable / @CachePut / @CacheEvict)缓存注解踩坑
java·经验分享·面试·ai编程
愿有所息8 小时前
记一次 ARM64 + JDK8 上 fastjson2 的诡异数据错乱
java
秋饼8 小时前
MCP 2026-07-28 无状态协议大改版:Spring AI 2.1 多轮往返请求与网关路由落地
java·ai·技术分享·后端开发
SL_staff9 小时前
JVS-BI 实战:业务人员如何用零代码 ELT + 可视化配置构建实时经营分析闭环
java·spring·全栈
SL_staff10 小时前
企业AI落地难?JVS-AI渐进式实践:安全可控、小切口验证、标准化沉淀与规模化复制
java·开源·aigc