C++中constexpr与函数参数转发的操作方法
作者:leapmotion
序
本文和大家探讨下关于constexpr的函数中参数的现象,以及如果参数是constexpr如何做转发
关于constexpr函数
constexpr是c++11引入的关键字,c++11的constexpr的函数中只是支持单句代码,c++14限制放宽,可以在里边写循环及逻辑判断等语句。
constexpr形容函数表示该函数被编译期计算或者调用。但是只能是可能被编译期计算,举例来说:
constexpr int square(int num) { return num * num; }
假设这里有一个计算平方的函数是constexpr形容,那么我如果这样调用:
int main() { int val = square(12); }
我们可以看下编译后的汇编:
main: push rbp mov rbp, rsp mov DWORD PTR [rbp-4], 144 // 这里 mov eax, 0 pop rbp ret
可以看到main函数里在运行时根本没有去调用square
函数,而是在编译期直接将square
函数计算得出144并赋值。也就是说传递参数是字面量(或者constexpr变量)可以被认定是编译期调用。
那么我们如果换一种方式调用呢?
int main() { int p = 12; int val = square(p); }
通过参数传递进来,再来看下汇编:
square(int): push rbp mov rbp, rsp mov DWORD PTR [rbp-4], edi mov eax, DWORD PTR [rbp-4] imul eax, eax pop rbp ret main: push rbp mov rbp, rsp sub rsp, 16 mov DWORD PTR [rbp-4], 12 mov eax, DWORD PTR [rbp-4] mov edi, eax call square(int) mov DWORD PTR [rbp-8], eax mov eax, 0 leave ret
略显复杂,不需要关注很多汇编代码,我们只需要知道运行时确实会去执行square函数。
constexpr函数小结
那也就是说constexpr函数并不是完全是编译期执行,编译器其实和你传递的参数或者函数内部的一些逻辑来判断。
constexpr参数传递
这里假设有一个这样的例子:
// ... struct AddSum { void add(int val) { constexpr auto sq = square(val); sum += sq; } int sum = 0; }; int main() { constexpr int p = 12; AddSum a; a.add(p); return 0; }
需要把一些编译期的数字的平方加起来,在add函数中使用constexpr
来接收square的返回值,因为这样可以使得square是编译期执行的,不过可惜的是这里不能通过编译,因为square不能确定val是不是constexpr
。但是我们明明传递的就是constexpr
的变量。那么问题来了,如何在传参数的时候能够将constexpr
的属性进行传递呢?
C++并不支持在形参使用constexpr限制:
// error: Function parameter cannot be constexpr void add(constexpr int val) { // ... }
且使用完美转发也不能将constexpr进行传递。
template<typename T> void add(T&& val) { constexpr auto sq = square(val); // error }
使用非类型模板参数
既然用到了模板,那么我们完全可以将val作为非类型的模板参数进行传递:
template<int val> void add() { constexpr auto sq = square(val); sum += sq; }
如果这里就结束的话,那确实不值得讨论了,非类型模板参数的问题是只能传递一些基本类型,我们自定的类constexpr对象则不能传递。
值内嵌的类型
其实我们可以借助一个helper类,将要传递的值封装在这里类里,然后需要的时候取出来。如下:
struct Helper { static constexpr int value() { return 12; } }; struct AddSum { template<typename H> void add(H) { constexpr auto sq = square(H::value()); sum += sq; } int sum = 0; }; int main() { AddSum a; a.add(Helper{}); return 0; }
可以看到我们Helper
中value用来封装了要传递的int值,然后将Helper的对象传递给add,进一步可以获取到value。
这里不能通用,因为我们需要在helper里写死这个值,又没办法将值传递给helper,感觉又绕回来了。哈哈,这里我们使用宏来定义一个通用的模式:
#define CONSTEXPR_HELPER(...) \ struct Helper { \ static constexpr decltype(auto) value() { \ return __VA_ARGS__; \ } \ }; int main() { AddSum a; using H = CONSTEXPR_HELPER(12); a.add(H{}); return 0; }
这种情况下就可以使用任意类型的的constexpr属性进行传递了。同时也可以将值在调用时传递而不是写死。
constexpr的lambda表达式
C++17出现了constexpr形式的lambda表达式,我们可以借助它来实现constexpr的传递
struct AddSum { template<typename H> void add(H h) { constexpr auto sq = square(h()); sum += sq; } int sum = 0; }; int main() { AddSum a; a.add([](){ return 12; }); return 0; }
这里我们使用constexpr的lambda对象传递给add
函数,同时传递过来的lambda表达式仍然具有lambda属性,然后调用将值获取到。
这样也就是在C++17版本不需要使用宏来定义内嵌值的类型。
总结
本篇文章就是和大家探讨下constexpr的参数传递的问题,可以使用非类型模板参数,宏定义,lambda表达式做到。
感谢大家,点个赞吧~
ref
https://mpark.github.io/programming/2017/05/26/constexpr-function-parameters/
到此这篇关于C++中constexpr与函数参数转发的文章就介绍到这了,更多相关constexpr函数参数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!