为什么要使用 constexpr
Why use constexpr
来个简单的SFINAE templating example
#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
值尚未声明 [=12=] 但它仍会在编译时获取其值。那么 constexpr
的用途是什么?为什么简单的静态变量在编译时获取它们的值?我如何知道哪些静态变量在编译时获得它们的值?
另外,我如何判断哪些值会在编译时求值,哪些不会?使用 constexpr
会保证编译时评估吗?如果不是,我怎么知道会发生什么(编译时间或 运行 时间)?
这不是一个很好的问题,因为 constexpr
是一个巨大的功能,它有很多 "points"。
最主要的是,与模板元编程语言相比,您可以使用(更多)普通 C++ 语法进行编译时计算。
例如,如果您曾尝试在编译时使用模板进行字符串操作,您可能会将这种体验与此处描述的一些技术进行对比:Conveniently Declaring Compile-Time Strings in C++
constexpr 基本上是一种新的编译时元编程语言,它与模板语言并行存在,并且像 constexpr 构造函数这样的东西允许您在编译时实例化结构,这对于仅使用模板是不可能的。
另一大 "point" 是您不必为编译时和 运行 时编写不同的代码。标记为 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;
}
如果函数 square()
上没有 constexpr
,则无法在 arr
的定义中调用它,因为数组大小需要作为编译时常量。
虽然您可以编写一个模板,让您的编译时间 square
先于 constexpr
,但您不能将该模板与非编译时间常量参数一起使用,因此您最终会得到编译时和非编译时版本的代码重复。对于大多数程序员来说,模板的语法比简单的函数定义更复杂,也更不熟悉。
事实上,constexpr
很少保证编译器何时会实际选择在编译时评估某些内容。在要求值是编译时常量(例如数组大小)的情况下,它当然是。在其他情况下,这在很大程度上取决于编译器——例如,在访问 arr[square(1)]
时调用 square()
,编译器可以在运行时自由评估,尽管实际上我希望大多数编译器在编译时评估它时间,至少在优化构建中。
来个简单的SFINAE templating example
#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
值尚未声明 [=12=] 但它仍会在编译时获取其值。那么 constexpr
的用途是什么?为什么简单的静态变量在编译时获取它们的值?我如何知道哪些静态变量在编译时获得它们的值?
另外,我如何判断哪些值会在编译时求值,哪些不会?使用 constexpr
会保证编译时评估吗?如果不是,我怎么知道会发生什么(编译时间或 运行 时间)?
这不是一个很好的问题,因为 constexpr
是一个巨大的功能,它有很多 "points"。
最主要的是,与模板元编程语言相比,您可以使用(更多)普通 C++ 语法进行编译时计算。
例如,如果您曾尝试在编译时使用模板进行字符串操作,您可能会将这种体验与此处描述的一些技术进行对比:Conveniently Declaring Compile-Time Strings in C++
constexpr 基本上是一种新的编译时元编程语言,它与模板语言并行存在,并且像 constexpr 构造函数这样的东西允许您在编译时实例化结构,这对于仅使用模板是不可能的。
另一大 "point" 是您不必为编译时和 运行 时编写不同的代码。标记为 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;
}
如果函数 square()
上没有 constexpr
,则无法在 arr
的定义中调用它,因为数组大小需要作为编译时常量。
虽然您可以编写一个模板,让您的编译时间 square
先于 constexpr
,但您不能将该模板与非编译时间常量参数一起使用,因此您最终会得到编译时和非编译时版本的代码重复。对于大多数程序员来说,模板的语法比简单的函数定义更复杂,也更不熟悉。
事实上,constexpr
很少保证编译器何时会实际选择在编译时评估某些内容。在要求值是编译时常量(例如数组大小)的情况下,它当然是。在其他情况下,这在很大程度上取决于编译器——例如,在访问 arr[square(1)]
时调用 square()
,编译器可以在运行时自由评估,尽管实际上我希望大多数编译器在编译时评估它时间,至少在优化构建中。