enable_if和类的偏特化

这篇文章是各个阶段的一些理解和想法,但我认为正确的理解在这里

md,感觉enable_if的推导过程又迷糊了。这次好像又清晰了一些-CSDN博客

另一篇:

一窥模板的替换和匹配方式:偏特化的参数比泛化版本的还要多:判断是不是std::pair<,>。-CSDN博客

fmt里判断是否为reference_wrapper-CSDN博客

【学习C++模板】从enable_if<true>来看偏特化,未指明的第二个参数到底是什么?_templete enable_if-CSDN博客

c++中enable_if和void_t的妙用_c++ enable_if判断第三个模板参数是不是void-CSDN博客

结合:C++ Insights  对模板进行展开

代码来自:

类模板相关知识-CSDN博客

偏特化的规则,总是比较难记,再研究下:

下面这段代码为什么会编译错误?

#include <iostream>
template<class T, class Enable >
class A {
public:
	A() { std::cout << "primary template\r\n"; }
}; // primary template

template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
public:
	A() { std::cout << "partial specialization\r\n"; }
}; // specialization for floating point types

int main() {
	A<double> a; //编译不过
}

error C2976: “A”: 模板 参数太少
note: 参见“A”的声明
error C2133: “a”: 未知的大小
error C2512: “A”: 没有合适的默认构造函数可用
note: 参见“A”的声明


2024.07.28  因为泛化版本里的参数都没能全部确定,所以报错。还没到去匹配偏特化的时候。 

 参数太少,是因为第二个参数没有直接或间接指定:

直接指定,就要这么写A<double,void> a;

间接指定,就需要在泛化版本里加上默认值,看下面。

 仿照着函数模板的方式,给偏特化版本一个默认参数呢,类似:

这种写法在函数里是可以的

template <typename Sequence, 
    typename std::enable_if<std::is_same<Sequence, int>::value,int >::type =0>
    void test451(Sequence x)
{
    std::cout << "1" << std::endl;
}

发现不支持这种写法,报错:error C2059: 语法错误:“=”

一个原因是:编译器首先要根据替换泛化版本里的参数来生成A<double, ?>,然后再去特化版本里去匹配,结果第一步就缺少参数。

第二个可能的原因是:应该是因为默认值需要在泛化版本里指定。而不应该在特化版本里指定。

#include <iostream>

template<class T, class Enabled >
class A {
public:
	A() { std::cout << "primary template\r\n"; }
}; // primary template

template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value,int>::type=0> {
public:
	A() { std::cout << "partial specialization\r\n"; }
}; // specialization for floating point types

int main() {
	A<double> a; //编译不通过
}


error C2059: 语法错误:“=”
error C2976: “A”: 模板 参数太少
note: 参见“A”的声明
error C2133: “a”: 未知的大小
error C2512: “A”: 没有合适的默认构造函数可用
note: 参见“A”的声明

2024.07.28 因为第二个参数,编译器把它当作函数f(T)
而typename std::enable_if<std::is_floating_point<T>::value,int>::type=0的写法,
可不是一个合格的函数写法。所以自然不合格,编译报错

放到上面呢,还是不行。这种从语法上就不行吧,A的尖括号里得有两个参数。

#include <iostream>
template<class T, class Enable >
class A {
public:
	A() { std::cout << "primary template\r\n"; }
}; // primary template

template<class T,typename std::enable_if<std::is_floating_point<T>::value,int>::type=0>
class A<T> {
public:
	A() { std::cout << "partial specialization\r\n"; }
}; // specialization for floating point types

int main() {
	A<double> a;
}

2024.07.28 这种写法,不符合偏特化的写法。

看一个“特化版本里的参数和泛化版本默认值不一致”的情况:

#include <iostream>

template<class T, class Enabled =void>
class A;

template<class T>
class A<T, typename/*这个typename不可少*/ std::enable_if<std::is_floating_point<T>::value,int>::type> {
public:
	A() 
	{ 
		std::cout << "partial specialization\r\n"; 
	}
}; // specialization for floating point types

int main() {
	
	A<double> a;
	int m = std::is_floating_point<double>::value;
	++m;
}

输出:
error C2079: “a”使用未定义的 class“A<double,void>”

说明编译器看到A<double>的时候先根据最泛化的版本生成A<double,void>,
然后看哪个特化版本能匹配上它。由于偏特化版本得到的结果是A<double,int>,
所以没匹配到偏特化版本

2024.07.28 理解,泛化版本为A<double, void>,面对偏特化的第一个参数,f(T)=T=double,
所以T类型为double,那么第二个,typename/*这个typename不可少*/ std::enable_if<std::is_floating_point<T>::value,int>::type的值(结果)自然也能推导出来,就是int
所以偏特化版本不合适。最后编译器认为泛化版本最匹配。
注意,在这个写法中,这个int没有在其他地方使用(或体现)

偏特化的写法,有的时候是这样:
A<T,f(T)>,这种情况下,泛化版本已经给出了T,所以f(T)的值可以推导出来
有的时候
A<true, f(T)>,这种秦广下,泛化版本给出了f(T)的值,所以需要通过值反推出T

-------------------------------------------------------------------

改一下,把泛型版本第二个参数的默认类型改为int:
template<class T, class Enabled =int>
class A;

template<class T>
class A<T, typename/*这个typename不可少*/ std::enable_if<std::is_floating_point<T>::value,int>::type> {
public:
	A() 
	{ 
		std::cout << "partial specialization\r\n"; 
	}
}; // specialization for floating point types

int main() {
	
	A<double> a;
	int m = std::is_floating_point<double>::value;
	++m;
}

运行结果为:
partial specialization

-------------------------------------------------------------------

对偏特化版本再改一下呢,看能否在推导中转成另外一个类型:
template<class T, class Enabled = double>
class A;

template<class T>
class A<T, typename/*这个typename不可少*/ std::enable_if<std::is_floating_point<T>::value, std::wstring>::type> {
public:
    A()
    {
        std::cout << "partial specialization\r\n";
    }
}; // specialization for floating point types

int main123() {

    A<double> a;
    int m = std::is_floating_point<double>::value;
    ++m;

    return 1;
}

编译错误为:
error C2079: “a”使用未定义的 class“A<double,double>”
说明了,还是先根据泛型的定义,推导成A<double,double>
然后再对偏特化进行检查,发现偏特化是A<double,std::wstring>,
可以看出,偏特化是在能匹配上<double,double>的前提下,才选偏特化。
是泛型定下了基调,偏特化被动去配合检查。

2024.07.28 跟上面分析一样,第一个参数,已经定下来是double,第二个自然就推导出来了。


--------------------------------------------------------

用A<double,std::wstring>去实例化,就可以了。但按我理解,第二个参数,其实本意是检查第一个参数
的,是一个约束,并不是真让它去参与实例化的:

template<class T, class Enabled = double>
class A;

template<class T>
class A<T, typename/*这个typename不可少*/ std::enable_if<std::is_floating_point<T>::value, std::wstring>::type> {
public:
    A()
    {
        std::cout << "partial specialization\r\n";
    }
}; // specialization for floating point types

int main123() {

    A<double,std::wstring> a;
    int m = std::is_floating_point<double>::value;
    ++m;

    return 1;
}
运行结果:partial specialization

2024.07.28 第一个参数f(T)=T=double
g(T)=typename/*这个typename不可少*/ std::enable_if<std::is_floating_point<T>::value, std::wstring>::type=typename/*这个typename不可少*/ std::enable_if<std::is_floating_point<double>::value, std::wstring>::type
=std::wstring

所以第二个参数,自然就被推导为std::wstring

----------------------------------------------------------

cpp insights对上面代码翻译的结果是:
#include <iostream>
#include <vector>
#include <string>
#include <tuple>

template<class T, class Enabled = double>
class A;
/* First instantiated from: insights.cpp:20 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class A<double, std::basic_string<wchar_t> >
{
  
  public: 
  inline A()
  {
    std::operator<<(std::cout, "partial specialization\r\n");
  }
  
};

#endif

template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value, std::wstring>::type>
{
  
  public: 
  inline A()
  {
    std::operator<<(std::cout, "partial specialization\r\n");
  }
  
};


int main123()
{
  A<double, std::basic_string<wchar_t> > a = A<double, std::basic_string<wchar_t> >();
  int m = static_cast<int>(std::integral_constant<bool, true>::value);
  ++m;
  return 1;
}

下面这个例子就是参数不够:

#include <iostream>

template<class T, class Enabled>
class A;

template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value,int>::type> {
public:
	A() 
	{ 
		std::cout << "partial specialization\r\n"; 
	}
}; // specialization for floating point types

int main() {
	
	A<double> a;
	int m = std::is_floating_point<double>::value;
	++m;
}

error C2976: “A”: 模板 参数太少
note: 参见“A”的声明
error C2079: “a”使用未定义的 class“A”

2024.07.28 对于泛化版本,参数给的不够。又没有默认参数

正确写法之一:

由于想用A<double>的形式,不想指定第二个参数,或者是想用第二个参数来进行sfinae。

所以,泛化版本的第二个参数,用的是默认值。

而特化版本的第二个参数,最好与泛化版本的默认值相同。这样的话,特化版本匹配不上的时候,最起码还能“退化”到泛化版本。

#include <iostream>
template<class T, class Enabled =void>
class A {
public:
	A() 
	{ 
		std::cout << "primary template\r\n"; 
	}
}; // primary template

template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
public:
	A() 
	{ 
		std::cout << "partial specialization\r\n"; 
	}
}; // specialization for floating point types

int main() {
	int m = std::is_floating_point<double>::value;
	++m;
	A<double> a;
}

输出:
partial specialization

 特化版本中推导出的第二个参数(int),和泛化版本(void)不一致时,

#include <iostream>
template<class T, class Enabled =void>
class A {
public:
	A() 
	{ 
		std::cout << "primary template\r\n"; 
	}
}; // primary template

template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value,int>::type> {
public:
	A() 
	{ 
		std::cout << "partial specialization\r\n"; 
	}
}; // specialization for floating point types

int main() {
	int m = std::is_floating_point<double>::value;
	++m;
	A<double> a;
}

输出:
primary template

当用A<double>进行实例化时,根据double这个参数,
偏特化版本推导出来应该是A<double,int>,但编译器读到A<double> a;
它期待的第二个参数其实是void,所以它会去匹配A<double,void>,
结果偏特化版本匹配不上,最后是泛化版本匹配成功。
所以输出结果为:primary template

特化版本中推导出的第二个参数(int),和泛化版本(int)一致时:

#include <iostream>
template<class T, class Enabled=int >
class A;

template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value,int>::type> {
public:
	A() { std::cout << "partial specialization\r\n";}
}; // specialization for floating point types

int main() {
	A<double> a;
}

编译通过,说明采用的是泛化版本
#include <iostream>
template<class T, class Enabled=void >
class A;

template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
public:
	A() { std::cout << "partial specialization\r\n";}
}; // specialization for floating point types

int main() {
	A<double> a;
}

编译通过

分析:

1、首先,想要实例化A,是需要两个参数的。要么直接给出,要么默认参数给出。

2、A<double> a;的写法,如果第二个参数不是默认值的话,肯定是报参数不足,它需要一个默认的参数来作为第二个参数。

3、假设第二个参数是默认的void,那么编译器读到A<double> a;时,它期望用A<double,void>作为原型去匹配(偏)特化版本,如果能匹配的上的话。如果匹配不上,那么就会采用泛化版本(如果泛化版本有实现的话)

4、如下的偏特化的写法,能匹配上的应该是A<double,void>。

template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {...};

5、下面的写法,能匹配上的应该是A<double,int>

template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value,int>::type> {...};

6、最泛型的版本必须是要有的,也就是template<class T, class Enabled=void>class A;

没有具体的实现,只是一个声明,都没问题。但如果完全没有它的话,编译器直接撂挑子不干。

7、偏特化版本的第二个模板参数,推导的结果最好和泛型版本的保持一致。这样的话,即使偏特化的不符合,也能“退化”到泛化版本可以选择。也就是说,void对void,int对int。你要是在偏特化版本里不写第二个参数,那么系统就会用泛型的默认参数类型

写成这样也行:

#include <iostream>
template<class T, class Enabled=int >
class A;

template<class T>
class A<T> {
public:
	A() { std::cout << "partial specialization\r\n";}
}; // specialization for floating point types

int main() {
	A<double> a;
}

8、所以,如果写成下面这样,A<double> a;既然没有直接给出,所以需要第二个参数,

几个要点:

1)、如果打算用第二个参数作为sfinae的选择,那么就可以将其设定一个默认值。在使用模板的时候不用直接写出来。

2)、如果泛化版本里用的是两个参数,那么(偏)特化版本中,类名右边的尖括号里,也应该是两个参数。

3)、泛化版本里可以用默认参数,偏特化版本里貌似不能用默认参数了。

4)、偏特化推导出的第二个参数,尽量和泛化版本的第二个默认参数保持一致。这样的话,偏特化推导出的结果不匹配的时候,编译器还可以选择泛化版本。

#include <iostream>
template<class T, class Enabled=void >
class A;

template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
public:
	A() { std::cout << "partial specialization\r\n";}
}; // specialization for floating point types

int main() {
	A<double> a;
}

9、偏特化的版本比最泛型版本的参数要少,所以要是不提供完整参数去匹配的话,那么就要给出默认参数。

百度搜索:c++ 偏特化 默认参数

AI的回答是:

// 主模板定义
template<typename T, typename U=int>
class MyClass {
public:
    void doSomething() {
        // 通用实现
    }
};
 
// 辅助模板(包装类)用于偏特化并提供默认参数
template<typename T>
class MyClass<T, int> {
public:
    void doSomething() {
        // 特化实现
    }
};
 
// 使用示例
int main() {
    MyClass<float, int> myObj; // 使用偏特化模板
    MyClass<double> myObj2;    // 使用偏特化模板,U使用默认参数int
    return 0;
}

但是,如果用默认参数的话,不能提供多个,否则就会出现编译器不知道选择哪个版本的情况:

#include <iostream>
template<class T, class dummy = void >
class A {
 public:
  A() { std::cout << "primary template\r\n"; }
}; // primary template

template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
 public:
  A() { std::cout << "partial specialization\r\n"; }
}; // specialization for floating point types

template<class T>
class A<T, typename std::enable_if<std::is_same<T,double>::value>::type> {
 public:
  A() { std::cout << "second specialization\r\n"; }
}; // specialization for floating point types

int main() {
  A<double> a;
  }

输出:
error C2752: “A<double,void>”: 多个部分专用化与模板参数列表匹配
note: 可能是“A<T,std::enable_if<std::is_floating_point<_Ty>::value,void>::type>”
note: 或    “A<T,std::enable_if<std::is_same<T,double>::value,void>::type>”


————————————————
 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/feng__shuai/article/details/131786228

所以,结论:

想用偏特化,就尽量别用默认参数?把参数一定要给全。

还有一种用法,貌似是用来判断类里是否存在变量或者函数定义。

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值