为什么要使用constexpr ?

Why use constexpr

本文关键字:constexpr 为什么      更新时间:2023-10-16

让我们以一个简单的SFINAE模板为例

#include <iostream>
template <typename T>
struct has_typedef_foobar {
    // Types "yes" and "no" are guaranteed to have different sizes,
    // specifically sizeof(yes) == 1 and sizeof(no) == 2.
    typedef char yes[1];
    typedef char no[2];
    template <typename C>
    static yes& test(typename C::foobar*);
    template <typename>
    static no& test(...);
    // If the "sizeof" of the result of calling test<T>(0) would be equal to sizeof(yes),
    // the first overload worked and T has a nested type named foobar.
    static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};
struct foo {    
    typedef float foobar;
};
int main() {
    std::cout << std::boolalpha;
    std::cout << has_typedef_foobar<int>::value << std::endl;
    std::cout << has_typedef_foobar<foo>::value << std::endl;
}

bool值没有被声明为constexpr,但它仍然在编译时获得其值。那么constexpr的使用是什么,为什么简单的静态变量在编译时得到它们的值?我如何知道哪些静态变量在编译时得到它们的值?

也我怎么能告诉哪些值将得到评估在编译时,哪些不会?使用constexpr能保证编译时的评估吗?如果不是,我怎么知道会发生什么(编译时或运行时)?

constexpr为您提供了以前不可能实现的关键功能之一,即在需要编译时常量的上下文中调用函数的能力。最简单的例子是数组大小:

#include <iostream>
using namespace std;
constexpr auto square(int x) { return x * x; }
int main()
{
    int arr[square(2)] = { 0, 1, 2, 3 };
    cout << arr[square(1)] << endl;
}

没有constexprsquare()函数,它不能在arr的定义中调用,因为数组大小需要是编译时常数。

虽然您可以编写一个模板,使您的编译时间square先于constexpr,但您不能将该模板与非编译时间常数参数一起使用,因此您将最终获得编译时间和非编译时间版本的代码重复。对于大多数程序员来说,模板的语法也比简单的函数定义更复杂,更不熟悉。

事实上,constexpr很少保证编译器在编译时何时会选择求值。在需要值为编译时常数(例如数组大小)的上下文中,它当然是。在其他情况下,这很大程度上取决于编译器-在访问arr[square(1)]时调用square(),例如,编译器可以在运行时自由评估,尽管在实践中,我希望大多数编译器在编译时评估这个,至少在优化的构建中。

这不是一个好问题,因为constexpr是一个巨大的功能,它有很多"点"。

最主要的一点是,你可以使用(更)普通的c++语法而不是模板元编程语言来进行编译时计算。

例如,如果您曾经尝试在编译时使用模板进行字符串操作,那么您可能会将这种体验与这里描述的一些技术进行对比:

constexpr基本上是一种新的编译时元编程语言,它与模板语言并行存在,像constexpr构造函数这样的东西允许你在编译时实例化结构体,这在模板中是不可能的。

另一个重要的"点"是,您不必为编译时和运行时编写不同的代码。标记为constexpr 的代码可以在编译时运行,也可以在运行时轻松运行。模板代码…必须完全在编译时运行,并且通常看起来与等效的运行时代码非常不同。因此,在某些情况下,使用constexpr代码更DRY。