如何在编译期强制计算常表达式?

11
几天前我询问编译器根据哪些标准来决定是否在编译时计算constexpr函数。
正如所发现的那样,只有当所有参数都是常量表达式,您要分配的变量也是常量表达式时,constexpr才会在编译时计算。
参考链接:When does a constexpr function get evaluated at compile time?
template<typename base_t, typename expo_t>
constexpr base_t POW(base_t base, expo_t expo)
{
    return (expo != 0 )? base * POW(base, expo -1) : 1;
}

template<typename T>
void foobar(T val)
{
    std::cout << val << std::endl;
}

int main(int argc, char** argv)
{
    foobar(POW((unsigned long long)2, 63));
    return 0;
}

如果我听到的是真的,那么这个代码示例非常不实用,因为 foobar 不接受 constexpr(由于某些原因,您无法将 constexpr 用作参数),POW 在运行时进行评估,尽管可以在编译时计算它。 强制编译时评估的明显解决方案是:

auto expr = POW((unsigned long long)2, 63);
foobar(expr);

然而,这迫使我使用额外的一行代码,在每次想要确保constexpr在编译时得到评估时都需要这样做。为了使这更加方便,我想出了以下可疑的宏:

#define FORCE_CT_EVAL(func) [](){constexpr auto ___expr = func; return std::move(___expr);}()
foobar(FORCE_CT_EVAL(POW((unsigned long long)2, 63)));

尽管它完全可以正常工作,但我感觉好像有些不对劲。创建匿名lambda函数会影响性能吗?通过rvalue引用返回是否实际上将表达式移动到函数参数中?std::move如何影响性能?有没有更好的一行代码解决方案?


Pubby在他对你之前的问题的回答中使用的模板技巧有什么问题? - Mat
2
啊,我明白了。(但我一点都不知道。)(无关的话:使用“2ULL”可以节省您的打字时间。) - Mat
那个lambda在返回时不需要std::move,因为___expr是一个xvalue,此外...如果你传递变量而不是字面值作为参数,我认为它不能正常工作,因为lambda需要捕获它们。现在的方式会发出有关无法捕获变量的错误,对编译评估执行没有太多建议性。 - oblitum
1
我认为更好的宏定义应该是 #define COMPILATION_EVAL(f, ...) (std::integral_constant<decltype(f(__VA_ARGS__)), f(__VA_ARGS__)>::value) - oblitum
1
请注意,在实践中,使用 -O2 选项,clang 编译器将代码在编译时进行计算。如果将其拆分为两行:const auto p=POW((unsigned long long)2, 63); foobar(p);,gcc 也会这样做。 - Marc Glisse
显示剩余11条评论
1个回答

6

只是为了不让它深埋在评论中:

#include <type_traits>

#define COMPILATION_EVAL(e) (std::integral_constant<decltype(e), e>::value)

constexpr int f(int i){return i;}

int main()
{
    int x = COMPILATION_EVAL(f(0));
}

编辑1:

这种方法有一个限制,constexpr函数可以接受浮点数并被分配给constexpr浮点变量,但你不能使用浮点类型作为非类型模板参数。同样,其他类型的文字也有相同的限制。

您的lambda对此也适用,但我想您需要一个默认捕获来获得有意义的错误消息,当非constexpr的内容传递到函数时。结尾处的std::move是可有可无的。

编辑2:

哦,抱歉,你的lambda方法对我不起作用,我刚才意识到,它怎么可能会工作,lambda不是一个constexpr函数。它对你也不应该有效。

看来确实没有办法绕过它,除了在局部范围内初始化一个constexpr变量。

编辑3:

哦,好的,我的错,lambda的目的只是评估。所以它对于那个是有效的。它的结果,反而是不可用于跟随另一个编译时间评估。

编辑4:

在C++17中,现在可以在constexpr上下文中使用lambda,因此在编辑2/编辑3中提到的限制被移除了!因此,lambda解决方案是正确的。查看此评论以获取更多信息。


2
Lambda不需要是constexpr。在constexpr中不需要使用COMPILATION_EVAL,它可以正常工作。 - zch
constexpr auto a = 42.2; auto res = FORCE_CT_EVAL(POW(a, 2)); std::cout << res << std::endl; 在GCC 4.7下运行良好。你用的是哪个编译器,chico?由于a在编译时已知,lambda表达式不必捕获它。 - Byzantian
尝试分配一个constexpr变量。@cyberpunk_,“自动res”是什么意思? - oblitum
@zch 我猜你没有理解问题的要点,请参考他之前的问题。这个问题是关于确保constexpr函数的编译时评估。 - oblitum
@chico 我不能这样做,因为 lambda 本身不是 constexpr。然而,这并不重要,因为我们想要强制的 constexpr 值是在 lambda 内部分配的。如果有一种将函数传递给模板的方法,我们就可以解决这个问题。 - Byzantian
@chico,如果我们能找到一种在编译时评估 lambda 的方法,那就太好了。 - Byzantian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接