模板元函数基本概念
支持在编译期调用并完成计算的函数即称为模板元函数,由于是在编译期完成,进而改善运行时的性能。元函数实际上即为C++中的一个模板类。
元函数的通常形式为:
template<typename T, typename Ts> // 元函数列表
struct MetaFunction { // 元函数名
using Type = some-type-define; // 返回的类型元数据
static constexpr int value = some-comile-time-cal // 返回的数值元数据
};
和函数调用一样,元函数调用也有形参、实参的概念,即template parameter 和template argument;
数值元函数
编译期计算斐波那契数列的第N项
template<size_t N>
struct Fibonacci {
static const size_t value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};
template<>
struct Fibonacci<0> {
static const size_t value = 0;
};
static_assert(Fibonacci<10>::value == 55);
其中Fibonacci即为一个数值元函数,在编译期计算值;
类型元函数
类型元函数在模板泛型编程中经常遇到,例如std::remove_reference:
template<class T> struct remove_reference { typedef T type; };
template<class T> struct remove_reference<T&> { typedef T type; };
template<class T> struct remove_reference<T&&> { typedef T type; };
混合元编程
这里混合:可理解为利用模板元函数在编译期生成代码,并在运行期执行生成后的代码;引用【3】中的代码为例子:
template<typename T,std::size_t N>
struct DotProductT
{
static inline T result(const T* a, const T* b)
{
return (*a)*(*b)+DotProductT<T, N-1>::result(a + 1, b + 1);
}
};
template<typename T>
struct DotProductT<T,0>
{
static inline T result(const T*, const T*)
{
return T{};
}
};
template<typename T,std::size_t N>
auto DotProductProcess(std::array<T,N> const& x,std::array<T,N> const& y)
{
return DotProductT<T,N>::result(x.begin(),y.begin());
}
std::array<int, 5> a{1, 2, 3, 4, 5};
DotProductProcess(a, a);
constexpr
constexpr在c++11引入,并在c++14和c++17中得到增强;经过constexpr声明的对象,和const修饰的对象一样,具有只读属性,并且变量作为编译器常量,但const对象不一定在编译期来初始化;
即所有constexpr对象都是const对象,而并非所有的const对象都是constexpr对象【2】;
1)constexpr修饰的变量【1】
1.its type must be a LiteralType. // 字面类型
2.it must be immediately initialized
3. the full-expression of its initialization, including all implicit conversions, constructors calls, etc, must be a constant expression // 常量表达式
自定义constexpr类型:
struct Dot {
constexpr Dot(int val) : m_val(val) { };
constexpr ~Dot() = default;
constexpr int GetVal() const
{
return m_val;
}
constexpr int SetVal(int val)
{
return m_val = val;
}
private:
int m_val;
};
constexpr Dot d(5); // compile OK
int val = 5;
constexpr Dot d(val); // compile error:初始化非常量表达式
int val = 5;
Dot d(val); // compile OK
3)constexpr修饰的静态成员变量,在C++17中自带inline属性,因此不需要再进行类外定义;
4)constexpr修饰的函数
当函数入参为常量时则在编译期完成计算;而入参为运行时变量则退化为普通函数;
constexpr int Add(int a, int b)
{
return a + b;
}
// 编译期
static_assert(Add(10, 10) == 20);
constexpr int result = Add(10, 10);
// 运行期
int a = 10;
static_assert(Add(a, a) == 20);
在C++11中,constexpr有较多的约束:
// C++11 constexpr functions use recursion rather than iteration,支持递归但不支持迭代
// in C++11, they must do so from the conditional operator ?:
// C++11 constexpr functions had to put everything in a single return statement
C++14进行了增强改进,constexpr函数内支持for循环迭代和函数临时变量:
constexpr int Add(int a, int b)
{
int result = 0;
for (int tmp = a; tmp <= b; tmp++) {
result += tmp;
}
return result;
}
C++17则支持编译器if 判断:
constexpr bool Compare(int a)
{
if constexpr (sizeof(a) < sizeof(char)) {
return true;
}
return false;
}
C++20开始vector/string可以在constexpr函数中编译期使用;
通过constexpr可以改造Fibonacci数列计算,性能上相比模板元函数更具优势(减少了模板实例化);
constexpr int Fibonacci(int n)
{
if (n == 0 || n == 1) {
return n;
} else {
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
}
注:在【2】中条款15:只要有可能使用constexpr,就使用它
consteval and constinit
consteval
通过constexpr修饰的函数在上一节中介绍既可以在编译期也可以在运行期运行;
consteval为C++20引入,其声明函数或函数模板为立即函数 ,即函数只支持在编译期运行,同时隐式声明为inline;
consteval int Add(int a, int b)
{
return a + b;
}
// 编译期
static_assert(Add(10, 10) == 20);
constexpr int result = Add(10, 10);
// 运行期
int a = 10;
Add(a, a); // 编译失败,consteval修饰的函数不支持运行期,非常量表达式
constinit
constinit
用于显式地指定变量的初始化方式为常量表达式 , 生命周期必须为静态生命周期或thread_local 生命周期 ;同时constinit
不具有只读属性,constexpr/const具有只读属性;
constinit int a = 10;
void Process()
{
static constinit int a = 10;
static constinit int b = Add(10, 10);
b = 20; // 不具有只读属性
constinit thread_local int c = 20;
}
枚举值与静态常量
在上文数值元函数中使用const修饰静态常量,此时类内常量在运行时并没有分配内存空间,简化例子为:
struct Test {
static const size_t value = 10;
};
void Process(const size_t&)
{
}
static_assert(Test::value == 10); // compile:OK
Process(Test::value); // compile error:undefined reference to `Test::value'
Process函数形参为左值引用,因此需要Test::value中的地址,此时则需要内存并且进行类外定义:
const size_t Test::value;
或者添加inline修饰:
struct Test {
static inline const size_t value = 10;
};
声明为 constexpr 的静态成员变量是隐式的内联变量 ,因此使用constexpr修饰也可以解决;
注:不同编译器现象可能有差别,在cppinsights 测试即使不用inline声明也可以获取左值地址
类内常量也可以修改为枚举:
struct Test {
enum { value = 10 };
};
void Process(const size_t&)
{
}
static_assert(Test::value == 10);
Process(Test::value);
枚举值不是左值,因此不存在上述所说的地址问题,但是局限于integral types,而constexpr并没有此局限性;
参考资料
【1】https://en.cppreference.com/w/cpp/language/constexpr
【2】Effective ModernC++
【3】C++ templates
标签:10,const,函数,constexpr,int,value,编译,计算 From: https://blog.51cto.com/u_13137973/6899729