当前位置: 首页 > news >正文

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

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

template<class T>
class TestClass
{
public:TestClass(){}//函数1template<class T1 = T, std::enable_if_t<std::is_same_v<int, T1>,bool> = true>TestClass(){//balabala}//函数2template<class T1 = T, std::enable_if_t<!std::is_same_v<int, T1>, bool> = true>TestClass(){//balabala}……
};int main() {TestClass<double> test;
} 

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

template<class T>
class TestClass
{
public:TestClass(){}//函数3template<std::enable_if_t<std::is_same_v<int, T>, bool> = true>TestClass(){}//函数4template<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

// 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

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

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


http://www.mrgr.cn/news/44181.html

相关文章:

  • Redis Stack十部曲之五:管理Redis
  • Android 组件化利器:WMRouter 与 DRouter 的选择与实践
  • 系统架构设计师论文《论SOA在企业集成架构设计中的应用》精选试读
  • 如何在 MySQL 中实现数据压缩
  • 【C++11】新特性
  • Linux网络命令:如何查看linux系统防火墙开放的端口有哪些?多种方法来查看系统开放的网络端口号,包括TCP端口和UDP端口
  • 日语发音
  • STM32驱动直流电机
  • Java IO流全面教程
  • 【AI知识点】如何对包含异常值的数据进行归一化处理?
  • 十大时间序列预测模型
  • Qwen大模型简介
  • PromQL:高效查询时间序列数据的利器
  • AcWing 662:点的坐标 ← 结构体 or 三目运算符
  • Lab4 【哈工大_操作系统】进程运行轨迹的跟踪与统计
  • Queue、Hashtable
  • 南昌网站建设让你的企业网站更具竞争力
  • 在CentOS7上安装mysql
  • 大数据分析的具体步骤
  • AtCoder Beginner Contest 374 (E + F)