在非constexpr环境中使用编译期计算
constexpr函数只有在一定上下文环境中才能在编译期计算,简单说就是需要通过定义一个constexpr变量来表明希望该函数在编译期执行。以一个编译期strlen函数为例:
template <typename Char>constexpr std::size_t constexpr_strlen(const Char* s) noexcept {std::size_t ret = 0;while (*s++) {++ret;}return ret;}int main() {auto len = constexpr_strlen("hello"); //运行期计算constexpr auto len1 = constexpr_strlen("hello"); //编译期计算}
没有加constexpr的变量len的计算将不会在编译期而是在运行期。
看一个在非constexpr环境里面使用constexpr_strlen的例子。
struct Dummy {Dummy(size_t val) : val_(val){}size_t val_;};#define GetDummy() Dummy(constexpr_strlen("hello"))int main() {Dummy d(constexpr_strlen("hello")); //运行期计算GetDummy(); //运行期计算}
因为Dummy的构造函数的参数不是constexpr修饰的,所以创建构Dummy的时候是没办法做编译期计算的,但是constexpr_strlen("hello")本身是可以在编译期计算的,这里存在一个问题:该如何在这种非constexpr环境中使用编译期计算?
一个简单的方法是写一个辅助函数,在函数中先生成constexpr变量再传给Dummy。
Dummy GetVal() {constexpr auto len = constexpr_strlen("hello");return Dummy(len);}int main() {Dummy d(constexpr_strlen("hello")); //运行期计算GetVal(); //编译期计算}
在不需要传入编译期常量的情况下这是可以的,如果希望传入一个编译期常量字符串并在编译期计算它的长度的时候要怎么写这个辅助函数呢?试试改造一下之前的GetVal函数,让它接受一个常量字符串。
template<size_t N>constexpr Dummy GetVal(const char (&str)[N]) {constexpr auto len = constexpr_strlen(str);return Dummy(len);}#define GET_VAL(str) GetVal(str)int main() {auto d2 = GetVal("hello");GET_VAL("hello");}
很遗憾,上面的代码编译器会报一个“表达式的计算结果不是常数”的编译错误,也就是说这个str不是一个编译期常量,这时候辅助函数也无法解决问题了,那么就这样白白浪费编译期计算的优化了吗?
让我们来用另外一个办法试试:
#define GET_VAL1(str) []{constexpr auto len = constexpr_strlen(str); return Dummy(len);}()int main() {GET_VAL1("hello"); //编译期计算!!}
这个宏就可以帮助我们实现编译期计算了,里面借助了一个lambda表达式来实现的,这个lambda表达式的实现比较关键,它并没有参数,而是通过宏直接生成编译期字符串从而保证了constexpr的所需的编译期计算的上下文环境。
可能有人觉得把这个lambda表达式提出来也可以,像这样:
[](const char* str) {constexpr auto len = constexpr_strlen(str); return Dummy(len); }("hello");
编译器同样会报一个“表达式的计算结果不是常数”的编译错误,原因和之前是一样的,因为lambda的参数不是一个constexpr变量。
通过将编译期常量传到宏函数中,由于宏的替换特性,在lambda表达式中就可以得到一个编译期常量,从而可以实现编译期计算。
借助宏函数和lambda可以实现在非constexpr上下文环境中实现编译期计算,避免了编译期计算退化为运行期计算。
关于运行期字符串的性能优化将会在下一篇文章中介绍,点赞数越多更新越快!




