C++标准模板库学习--函数模板返回值参数类型
template<typename T1, typename T2>
2 T1 max (T1 a, T2 b)
3 {
4 return b < a ? a : b;
5 }
6 ...
7 auto m = ::max(4, 7.2); // OK, 不过返回类型与第一个参数类型一样
如何解决模板的返回类型
法一,使用decltype进行类型推断,在编译时确定返回值类型,就能根据实例化时参数类型确定了。
template<typename T1, typename T2>
auto max1(T1 a, T2 b) -> decltype(a < b ? b : a)
{
return a < b ? b : a;
}
法二,直接使用C++14的特性auto,进行类型自动推导
编译器可以自动推断函数的返回类型,无需使用尾随返回类型。
template<typename T1, typename T2>
auto max2(T1 a, T2 b)
{
return a < b ? b : a;
}
法三,使用模板元编程工具——公共类型std::common_type
介绍common_type
std::common_type
是 C++ 标准库 <type_traits>
头文件中提供的一个模板元编程工具,用于确定一组类型的公共类型。它的主要作用是在编译时找出能够表示所有给定类型值的最通用类型。
在处理模板编程时,经常会遇到需要对不同类型进行操作并返回一个合适类型的情况,std::common_type
就可以帮助我们自动推断出这个合适的类型。例如,当你有一个模板函数,需要对不同类型的参数进行比较或运算,然后返回一个合适的结果类型,std::common_type
就非常有用。
如何使用
1,获取公共类型
using common_type = std::common_type_t<int, double>;
std::cout << "common_type of int and double is: " << typeid(common_type).name() << std::endl;
2,确定合适的函数返回类型
template<typename T1,typename T2>
typename std::common_type<T1, T2>::type max3(T1 a, T2 b)
{
return a < b ? b : a;
}
int main()
{
using common_type = std::common_type_t<int, double>;
std::cout << "common_type of int and double is: " << typeid(common_type).name() << std::endl;
std::cout << max1(4, 7.2) << std::endl;
std::cout << max2(4, 7.2) << std::endl;
std::cout << max3(4, 7.2) << std::endl;
return 0;
}
关键:如何自己去实现common_type 和其简化common_type_t
1,先学std::add_rvalue_reference
std::add_rvalue_reference
是 C++ 标准库 <type_traits>
头文件中的一个模板元编程工具,属于类型特征(Type Traits)库的一部分。它的主要作用是在编译时为给定的类型添加右值引用(&&
)。下面从定义、使用方法、实现原理等方面详细介绍。
定义和作用
std::add_rvalue_reference
是一个模板结构体,它接受一个类型参数 T
,并通过 ::type
成员来提供添加右值引用后的类型。其主要用途是在模板编程中根据需要修改类型,特别是在进行类型推导和泛型编程时,确保类型具有合适的引用属性。
使用方法
int main()
{
using Rvalueint = std::add_rvalue_reference<int>::type;
using Rvalueint2 = std::add_rvalue_reference<int&>::type;
std::cout<<std::boolalpha;
cout<<"Is Rvalueint a reference? "<<std::is_reference<Rvalueint>::value<<endl;
cout<<"Is Rvalueint2 a reference? "<<std::is_reference<Rvalueint2>::value<<endl;
using Rvalueint3 = std::add_rvalue_reference<int&&>::type;
cout<<"Is Rvalueint3 a reference? "<<std::is_reference<Rvalueint3>::value<<endl;
return 0;
}
用来生成引用类型,如果已经是引用类型,类型不变。
具体实现
//the add_rvalue_reference trait
//the basic definition
template<typename T>
struct my_add_rvalue_reference
{
using type = T&&;
};
//Specialization: When T is already an rvalue reference, keep it unchanged.
template<typename T>
struct my_add_rvalue_reference<T&&>
{
using type = T&&;
};
//Alias template for simplifying type access
template<typename T>
using my_add_rvalue_reference_t = typename my_add_rvalue_reference<T>::type;
2,再了解std::declval
std::declval
是 C++ 标准库中的一个工具,用于在未求值语境中获得对象的引用,主要配合 decltype
进行编译时类型推导。它不会在运行时被调用,只是为了帮助编译器分析类型。下面从标准库中的声明、实现原理、模拟实现等方面来详细介绍。
标准库中的声明
在 C++ 标准库中,std::declval
通常在 <utility>
头文件中声明,其声明形式如下
template< class T >
typename std::add_rvalue_reference<T>::type declval() noexcept;
实现原理
std::declval
实际上并没有真正的函数定义,它只有声明。因为它的目的是在编译时进行类型推导,并不需要在运行时执行具体的代码逻辑。如果在运行时尝试调用 std::declval
,会导致链接错误,因为找不到对应的函数定义。编译器会在编译过程中处理 std::declval
的使用,利用它来辅助进行类型分析。
为什么没有函数的定义?
设计目的决定
std::declval
的设计目的是在编译时辅助进行类型推导,而不是在运行时执行具体的操作。它主要用于在不实际构造对象的情况下,让编译器分析对象的类型和相关表达式。如果为其提供函数定义,就意味着在运行时需要执行该函数,这与它的设计初衷相悖。
避免副作用
如果 std::declval
有函数定义,在调用时就会涉及对象的构造、析构等操作,可能会产生不必要的副作用。例如,对于一些构造函数有复杂逻辑或者构造函数被删除的类型,调用构造函数可能会导致编译错误或者意外的行为。通过只声明不定义,std::declval
避免了这些问题,确保只在编译时用于类型推导。
链接错误作为保护机制
由于 std::declval
没有定义,在运行时调用它会导致链接错误。这实际上是一种保护机制,提醒开发者 std::declval
只能用于编译时的类型分析,不能在运行时使用。
declval用于未求值语境(未求值语境(unevaluated context)是指在编译过程中,某些表达式只用于类型检查和推导,而不会被实际求值的情况。在未求值语境中,表达式不会产生运行时的效果,编译器只会分析其类型信息。)获取传入参数类型。
3,comon_type具体实现
template<typename T1,typename T2>
using Common_type = decltype(true ? declval<T1>() : declval<T2>());
解释为什么使用到三目运算和declval
在 C++ 中,三元运算符(三目运算符)?:
有一套明确的类型转换规则。当使用 true ? expr1 : expr2
时,编译器会根据 expr1
和 expr2
的类型来确定整个表达式的类型。具体规则如下:
- 如果
expr1
和expr2
类型相同,那么整个表达式的类型就是该类型。 - 如果
expr1
和expr2
类型不同,编译器会尝试进行隐式类型转换,找到一个能够同时表示expr1
和expr2
值的公共类型。例如,对于int
和double
类型,由于double
可以表示int
类型的值,编译器会将int
类型的值转换为double
类型,因此整个表达式的类型就是double
。
std::declval
用于在不实际构造对象的情况下获取对象的引用,而 decltype
用于在编译时推断表达式的类型。通过将 std::declval<T1>()
和 std::declval<T2>()
作为三元运算符的两个操作数,我们可以利用三元运算符的类型转换规则,让编译器在编译时推断出 T1
和 T2
的公共类型。
使用如下
using Type = Common_type<int, double>;
std::cout << "Type is " << typeid(Type).name() << std::endl;