前面介绍过if constexpr这后头就跟上了if consteval,说白了,就是前有车,后面就得跟上辙,成套成系列的。无论是在模板编程还是在元编程中,都会有编译期处理和运行期处理的一些需求,这些需求是产生这些技术的根本的要求,看来c++在这条路上想一条路跑到黑了。
在c++20中,介绍过consteval和std::is_constant_evaluated()这两个特性,前者确定在编译期的展开执行,后者是提供一个判断是否在编译期执行的函数。比如在前面学习可以知道constexpr函数既可以在编译期也可以在运行期执行,那么这个判断函数就可以派上用场了。
为了能更好的展示if consteval的用法,先从一个小的例程开始:
#include
#include
#include
constexpr int Add(std::span sp)
{
if (std::is_constant_evaluated())
{
int r = 0;
for (auto au : sp) { r += au * au; }
return r;
}
else
{
__asm {mov al, 2};
}
}
int main()
{
std::array arr = {1,2,3};
Add(std::span{arr});
}
这时候儿会不会想到可以考虑一下if constexpr这个特性,也就是说,用提到的这个方法来做一些判断:
#include
#include
#include
consteval int Compiler(std::span sp)
{
int r = 0;
for (auto au : sp) { r += au * au; }
return r;
}
constexpr int Add(std::span sp)
{
//注意下面两行
if (std::is_constant_evaluated())
//if constexpr (std::is_constant_evaluated())
{
return Compiler(sp);
}
else
{
__asm {mov al, 2};
}
}
int main()
{
std::array arr = {1,2,3};
Add(std::span{arr});
}
恭喜,你收获了一个错误,而且这个错误有点莫名其妙(" 对即时函数的调用不是常量表达式")。原因在于开发者想当然的认为判断一下然后再决定是运行期还是编译期,可问题是,你的判断很可能已经在运行期了,这和你已经上大学了,再让你填写高考志愿,有点过分了啊。
那么有些人会马上想到if constexpr,这个可是好东西啊,正如上面的注释掉的那行,然后解开,把上面的那行代码注释。可仔细一想,这个constexpr if是确定性的在编译期执行,那么你再加一层判断又有什么用处呢?所以就得增加一个方法来解决这个问题,于是乎今天要讲的if consteval出现了。
在c++23中就可以使用下面的代码(下面的代码使用GNU c++编译器,注意和VC不同):
#include
#include
#include
#include
#include
consteval int Compiler(std::span sp)
{
int r = 0;
for (auto au : sp) { r += au * au; }
return r;
}
constexpr int Add(std::span sp)
{
if consteval
{
return Compiler(sp);
}
else
{
__asm__ ("mov 2,%al");//AT&T
}
}
int main()
{
std::vector vec{1,2,3};
std::span sp{vec};
Add(sp);
}
可惜的是,VC最新的编译器支持版本也编译不过去。不过在cppreferece上可以运行,这个是最新的,不过注意VC与GNU的不同。
再看一个cppreference.com上的例子:
#include
#include
#include
#include
constexpr bool is_constant_evaluated() noexcept
{
if consteval { return true; } else { return false; }
}
constexpr bool is_runtime_evaluated() noexcept
{
if not consteval { return true; } else { return false; }
}
consteval std::uint64_t ipow_ct(std::uint64_t base, std::uint8_t exp)
{
if (!base) return base;
std::uint64_t res{1};
while (exp)
{
if (exp & 1) res * = base;
exp /= 2;
base * = base;
}
return res;
}
constexpr std::uint64_t ipow(std::uint64_t base, std::uint8_t exp)
{
if consteval // use a compile-time friendly algorithm
{
return ipow_ct(base, exp);
}
else // use runtime evaluation
{
return std::pow(base, exp);
}
}
int main(int, const char* argv[])
{
static_assert(ipow(0,10) == 0 && ipow(2,10) == 1024);
std::cout << ipow(std::strlen(argv[0]), 3) << '\n';
}
这个在网站上就可以运行。
学习新的标准的最大痛点在于编译器的支持。这个目前没有太好的方法,可以在一些网站上运行,也可以自己下载最新版本的编译来运行,这个就看个人的喜好。新的东西意味着不太稳定,所以还是先掌握趋势,对于在实际工程中,不要急于应用。
大胆学习,谨慎应用。