为什么要使用 constexpr

4

让我们来看一个简单的 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是否保证了编译时计算?如果不是,如何知道哪些会发生(编译时或运行时)?


你应该添加来自链接的相关代码,以防将来发生更改。 - Tas
2个回答

12

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;
}

如果在函数square()上没有使用constexpr,那么它无法在arr的定义中调用,因为数组大小必须是编译时常量。

虽然您可以编写一个模板来提供编译时的square,但是您无法将该模板与非编译时常量参数一起使用,因此您最终会在编译时和非编译时版本之间产生代码重复。模板语法比简单的函数定义更复杂,对大多数程序员来说也不太熟悉。

实际上,constexpr很少保证编译器何时会选择在编译时计算某个值。在需要一个编译时常量的情况下(例如数组大小),当然会在编译时计算。在其他情况下,这主要取决于编译器 - 例如,在访问arr[square(1)]时调用square(),编译器可以自由地在运行时进行评估,尽管在实践中,我希望大多数编译器至少在优化构建中会在编译时计算此内容。


你能举个例子,说明如何使用模板来完成这个任务吗? - user4386938
1
@AaryamanSagar http://coliru.stacked-crooked.com/a/e8a6dd15a3540b1e 给出了一个同时使用constexpr函数和模板的示例。 - mattnewport
谢谢!但是我困惑的部分是哪些静态变量在编译时解析,哪些不是。但我猜编译器只会选择那些已经内联的变量,并在编译时为它们赋值。 - user4386938
@AaryamanSagar 在编译时没有太多保证能解决的问题。基本上,如果一个表达式用于需要编译时常量的上下文中(例如模板参数、数组大小、alignas 参数、枚举值初始值和其他一些情况),那么它会在编译时计算。在任何其他不需要编译时常量的情况下,表达式是否在编译时计算取决于编译器,这可能取决于优化设置等因素。 - mattnewport
是的,这正是我想的。谢谢你的回答! - user4386938

7

这不是一个很好的问题,因为constexpr是一个庞大的功能,它有许多“要点”。

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

例如,如果您曾经尝试使用模板在编译时进行字符串操作,则可能会通过此处描述的某些技术与该经验形成对比:Conveniently Declaring Compile-Time Strings in C++

constexpr基本上是一种新的编译时元编程语言,它存在于模板语言的并行之中。一些东西,如constexpr构造函数,允许您在编译时实例化结构体,这仅使用模板是不可能的。

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


那并没有真正回答我心中的问题。我编辑了我的问题,希望现在对其他人更加清晰。 - user4386938
好的,我认为你现在有多个问题。但是“那么重点是什么”这个问题已经被我的回答很好地解答了。当然,使用模板可以获得编译时布尔值,但不能获得编译时结构体。而且,模板代码有时不够DRY。 “我怎样才能知道哪些实际上是编译时的,哪些不是”这是一个完全不同的问题。 - Chris Beck
谢谢你的回答! - user4386938
3
提高了。我只是认为第一行应该写成“这一个很棒的问题”,就是出于这个原因。 - Victor Sergienko

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