类型萃取中的decltype及模板函数特化特性

目录

前言

一、decltype关键字

1.获取变量类型

2.获取表达式类型

3.获取表达式(函数)类型

4.用于代理模式中完美转发函数返回值类型

二、模板的偏特化与全特化

三、函数也能做函数参数

总结


前言

        本文将从各方面讲解C++11中提出的decltype关键字的用处以及模板函数在同时具有特化及偏特化版本时,编译器对函数调用的选择方法,可以帮助读者更好地认识模板函数的调用以及自动类型推导在模板类型中的妙用。此外,我们还将探讨函数作为函数参数的应用。


一、decltype关键字

        当我们需要获取一个表达式的类型时,可以使用decltype关键字来推断表达式的类型。decltype关键字在C++11中引入,它可以在编译时获取表达式的类型,包括变量、函数调用、表达式等。

注意上面decltype关键字的作用中有两个关键点:

  1. 编译时获取表达式的类型
  2. 编译时获取表达式的类型

因为之前网络有个梗:“请利用模板元编程实现编译期堆排序”

这里就用到了decltype关键字可以在编译时就确定表达式的类型。

前情提要:
类型萃取 type_traits 为C++提供的元编程工具,例如类型特征、编译时有理算术和编译时整数序列。

这里需要先知以下几个模板工具(C++14中利用using起别名简化模板类的使用):

  • remove_const_t<类型名>                 移除类型中的const修饰符
  • remove_reference_t<类型名>          移除类型中的reference修饰符
  • decay_t<类型名>                              朽化类型(同时移除const和reference)
  • is_same_v<T1, T2>                          判断T1和T2是否为同类型

接着来看decltype关键字的用法:

decltype会返回表达式的静态类型,包括修饰符

1.获取变量类型

decltype(exp) 这里exp可以是不被()包围的表达式,或是一个类成员访问表达式,或者是一个单独的变量,那么推导类型就和exp一致

void test1()
{
	// decltype(表达式)
	// 注意表达式和变量之间的区别  可以给变量加括号使得强行编译为表达式
	//1. 获取变量的类型:
	constexpr int num1 = 10;
	decltype(num1) num2 = 92;   // decltype(变量) 推断时类型确定以num1变量类型为标准
	cout << is_same_v<const int, decltype(num2)> << endl;   // 1     推断出num2类型为const int
	cout << is_same_v<int, decltype(num2)> << endl;    // 0
	cout << is_same_v<int, remove_const_t<decltype(num2)>> << endl;    // 1
	cout << is_same_v<int, decay_t<decltype(num2)>> << endl;    // 1
}

2.获取表达式类型

decltype(exp) 这里exp如果是一个左值,或者被()包围,那么推导类型就是exp的引用

//2. 获取表达式的类型:
	constexpr int num11 = 10;
	decltype((num11)) num22 = num11;   // (num22)可以让编译器认为这是表达式,不是变量
	cout << is_same_v<const int, decltype(num22)> << endl;   // 0
	cout << is_same_v<int&, decltype(num22)> << endl;    // 0
	cout << is_same_v<const int&, decltype(num22)> << endl;    // 1    推断出num22类型为const int&
	// **********************************
	// 注意这里remove_const_t修改的const修饰符作用在了num22引用指向的对象上  对num22的类型推导仍为const int&
	cout << is_same_v<const int&, remove_const_t<decltype(&num22)>> << endl;   // 1
	cout << is_same_v<int&, remove_const_t<decltype(num22)>> << endl;   // 0
	// **********************************
	cout << is_same_v<const int, remove_reference_t<decltype(num22)>> << endl;   // 1
	cout << is_same_v<int, decay_t<decltype(num22)>> << endl;   // 1

3.获取表达式(函数)类型

decltype(exp) 这里exp如果是函数调用,那么推导类型就和函数返回值类型一致

首先给出自动推导返回值类型的函数:

decltype(auto) get_id()   // 自动推导可以保留引用
{
	const string s = "s_t_r_i_n_g";
	return s;
}

调用接收:

//3. 获取函数返回值的类型:
decltype(get_id()) id;

调试窗口:

不要在意变量id的内容为空,因为你没有给他初始化赋值,但是可以看到id的类型与函数返回值类型一致为string,推导正确。

4.用于代理模式中完美转发函数返回值类型

首先给出代理模式的简单实现模板:

template<typename T>
class RealObject
{
public:
	RealObject(){}
	RealObject(T val):m_val(val){}
	void setValue(T val)
	{
		m_val = val;
	}
	T getValue()
	{
		return m_val;
	}
private:
	T m_val;
};
template<typename... T>
class ProxyObject
{
private:
	RealObject<T...> realobject;
public:
	ProxyObject(T... val) { realobject.setValue(forward<T...>(val)...); }
	template<typename... Arg>
	decltype(auto) getValue(Arg&... arg)   // 利用decltype(auto)可以方便实现返回值的自我推导
	{
		return realobject.getValue();
	}
};

给出实例化并利用auto变量自动推导接收函数返回值的代码:

//4. 代理模式中用于完美转发函数返回值:
const vector<int> v{1, 2, 3, 4, 5};
ProxyObject proObj(v);     // 创建代理对象
const auto val = proObj.getValue();
cout << "value = " << val << endl;

由于这里需要直接cout输出vector对象,所以我们需要对应vector容器全局重载左移运算符

template<typename T>
ostream& operator<<(ostream& os,const vector<T>& v)
{
	os << '{';
	for(size_t i= 0;i<v.size();++i)
	{
		os << v.at(i);
		if (i != v.size() - 1) { os << ", "; }
	}
	os << '}';
	return os;
}

运行结果:

可以看到代理模式可以用于保护被代理对象,限制对其敏感操作的访问,只提供有必要的接口,同时利用 decltype(auto) 成功实现了代码复用,以实现多类型的函数返回值要求。

二、模板的偏特化与全特化

首先给出模板函数及其偏特化、全特化后的函数:

template<typename T,typename N>
void print()
{
	cout << "模板类型" << endl;
}
template<typename T,int N = 0>
void print()
{
	cout << "偏特化" << endl;
}
template<char C = 'c', int Size = 0>
void print()
{
	cout << "全特化" << endl;
}

接着给出调用测试部分代码:

void test2()
{
	int* p = new int[10];
	int size1 = _msize(p)/sizeof(p[0]);
	cout << "size1 = " << size1 << endl;
	    //const int size2 = sizeof(p) / sizeof(p[0]);  // 动态数组错误大小求法
	    //cout << "size2 = " << size2 << endl;
	constexpr double b = 10.8;
	print<char, decltype(b)>();
	print<char, decltype(size1)>();
	print<char>();
	print<char, 100>();
	//print<int, size1>();   size1的值在运行时才能确定,所以该语句编译报错
	print<>();
    print();
	print<'w', 8>();
	delete[] p;

}

运行结果:

可以看到匹配全特化和偏特化函数的模板指定类型同样可以调用普通函数模板,但是优先调用了全特化、偏特化函数,固然存在一种优先调用规则来决定函数的调用选择。

优先调用规则如下:

  • 如果存在完全匹配的全特化,将优先选择全特化进行调用。
  • 如果不存在完全匹配的全特化,将选择最匹配的偏特化进行调用。
  • 如果存在多个偏特化同样匹配,或者没有偏特化,将选择最匹配的普通函数模板进行调用。

三、函数也能做函数参数

首先给出打印数字的函数:

void printNum(const int num)
{
	cout << num << endl;
}

接着给出基于模板元编程的利用函数做函数参数的代码及调用实例:

template<typename... T>
void do_N_case(auto func(T...), const int N)
{
	for(int i = 0;i<N;++i)
	{
		func(i);
	}
}
void test3()
{
	do_N_case<int>(printNum, 8);
}

运行结果:

该模块可以结合lambda表达式灵活应用,具体拓展等方面的知识点在后面文章会详细进行解释。 


总结

        通过本篇博客的学习,我们深入了解了decltype关键字的用法和应用场景,以及模板的偏特化与全特化的灵活性。我们还学会了如何将函数作为函数参数,从而实现更灵活的代码设计和调用方式。这些知识和技巧将有助于我们在C++编程中更好地应对复杂的类型推导和定制化处理的需求。希望本篇博客能够对读者有所帮助,提供了一些有用的技术和思路。如果您对这些内容有任何疑问或者需要进一步的探讨,请随时提问。

 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
模板元编程类型技术是一种在编译时获类型信息的技术。它允许在编译期间根据类型的属性或征来进行条件判断和编译时的优类型的常见应用场景是在函数模板或类模板根据不同类型征来选择不同的实现或行为。通过类型,可以在编译期间根据类型的属性来进行逻辑判断,从而生成不同的代码。 常见的类型技术包括使用、偏和SFINAE(Substitution Failure Is Not An Error)等技术。 是指针对类型提供单独的实现或行为。可以通过来为类型定制代码。例如: ```cpp template <typename T> struct TypeTrait; template <> struct TypeTrait<int> { static const bool isInt = true; }; template <> struct TypeTrait<float> { static const bool isInt = false; }; ``` 在上述示例,通过`TypeTrait`模板,为`int`类型和`float`类型提供了不同的属性`isInt`。 偏是在泛模板的基础上,对某些类型进行殊处理。通过偏,可以根据类型征来选择不同的实现。例如: ```cpp template <typename T> struct TypeTrait { static const bool isInt = false; }; template <typename T> struct TypeTrait<T*> { static const bool isInt = true; }; ``` 在上述示例,通过偏,为指针类型提供了殊的属性`isInt`。 SFINAE(Substitution Failure Is Not An Error)是一种利用编译器的重载解析规则来实现类型的技术。通过重载和模板参数匹配失败,可以进行条件判断。例如: ```cpp template <typename T> struct TypeTrait { template <typename U> static std::true_type test(decltype(&U::getApi)); template <typename U> static std::false_type test(...); static constexpr bool value = decltype(test<T>(nullptr))::value; }; ``` 在上述示例,通过`test`函数模板的重载和`decltype`关键字,可以判断类型`T`是否具有`getApi`成员函数,并返回对应的`std::true_type`或`std::false_type`。 通过这些技术,可以在编译期间根据类型的属性来进行条件判断和选择不同的实现。这种类型技术在模板元编程经常被使用,可以提高代码的灵活性和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

螺蛳粉只吃炸蛋的走风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值