Language Feature | Proposal | Available in GCC? | SD-6 Feature Test |
Relaxing requirements on constexpr functions | N3652 | 5 | __cpp_constexpr >= 201304 |
介绍
C++11引入了声明为constexpr的函数的概念。声明为constexpr函数的意义是:如果其参数均为合适的编译期常量,则对这个constexpr函数的调用就可用于期望常量表达式的场合(如模板的非类型参数,或枚举常量的值)。如果参数的值在运行期才能确定,或者虽然参数的值是编译期常量,但不符合这个函数的要求,则对这个函数调用的求值只能在运行期进行。 然而C++11要求constexpr函数只含有一个将被返回的表达式(也可以还含有static_assert声明等其它语句,但允许的语句类型很少)。
C++14放松了这些限制。声明为constexpr的函数可以含有以下内容:
- 任何声明,除了:static或thread_local变量;没有初始化的变量声明。
- 条件分支语句if和switch。
- 所有的循环语句,包括基于范围的for循环。
- 表达式可以改变一个对象的值,只需该对象的生命期在声明为constexpr的函数内部开始。包括对有constexpr声明的任何非const非静态成员函数的调用。
但goto仍然不允许在constexpr函数中出现。此外,C++11指出,所有被声明为constexpr的非静态成员函数也隐含声明为const(即函数不能修改*this的值)。这点已经被删除,非静态成员函数可以为非const。
constexpr 说明符语法参考:https://zh.cppreference.com/w/cpp/language/constexpr
例子1,上下文是编译期常量的地方
constexpr 说明符声明编译时可以对函数或变量求值值。这些变量和函数(给定了合适的函数实参的情况下)即可用于需要编译期常量表达式的地方。
g++ -std=c++14 -fconstexpr-loop-limit=600000 -o n3652 n3652.cpp
#include <iostream>
#include <chrono>
constexpr long long addition(long long num)
{
long long sum = 0;
for (int i = 0; i <= num; i++)
{
sum += i;
}
return sum;
}
int main(int argc, char* argv[])
{
//test1
auto start1 = std::chrono::steady_clock::now();
//不需要编译期常量上下文
std::cout << addition(500000000); //500 mill //executes in 1.957 seconds
auto stop1 = std::chrono::steady_clock::now();
auto dur1 = std::chrono::duration_cast<std::chrono::milliseconds>(stop1 - start1);
std::cout << "\n\nIt took " << static_cast<double>(dur1.count()) / 1000 << " seconds!" << std::endl;
//test2
auto start2 = std::chrono::steady_clock::now();
//需要编译期常量上下文
constexpr auto res = addition(500000); std::cout << res; //500 mill //executes in 0 seconds
auto stop2 = std::chrono::steady_clock::now();
auto dur2 = std::chrono::duration_cast<std::chrono::milliseconds>(stop2 - start2);
std::cout << "\n\nIt took " << static_cast<double>(dur2.count()) / 1000 << " seconds!" << std::endl;
return 0;
}
例子2,C++14语法和C++11语法对比
#include <iostream>
#include <stdexcept>
// C++11 constexpr 函数使用递归而非迭代
// (C++14 constexpr 函数可使用局部变量和循环)
constexpr int factorial(int n)
{
return n <= 1 ? 1 : (n * factorial(n - 1));
}
// 字面类
class conststr {
const char* p;
std::size_t sz;
public:
template<std::size_t N>
constexpr conststr(const char(&a)[N]): p(a), sz(N - 1) {}
// constexpr 函数通过抛异常来提示错误
// C++11 中,它们必须用条件运算符 ?: 来这么做
constexpr char operator[](std::size_t n) const
{
return n < sz ? p[n] : throw std::out_of_range("");
}
constexpr std::size_t size() const { return sz; }
};
// C++11 constexpr 函数必须把一切放在单条 return 语句中
// (C++14 无此要求)
constexpr std::size_t countlower(conststr s, std::size_t n = 0,
std::size_t c = 0)
{
return n == s.size() ? c :
'a' <= s[n] && s[n] <= 'z' ? countlower(s, n + 1, c + 1) :
countlower(s, n + 1, c);
}
// 输出要求编译时常量的函数,用于测试
template<int n>
struct constN
{
constN() { std::cout << n << '\n'; }
};
int main()
{
std::cout << "4! = " ;
constN<factorial(4)> out1; // 在编译时计算
volatile int k = 8; // 使用 volatile 防止优化
std::cout << k << "! = " << factorial(k) << '\n'; // 运行时计算
std::cout << "\"Hello, world!\" 里小写字母的个数是 ";
constN<countlower("Hello, world!")> out2; // 隐式转换到常量字符串
}
例子3,constexpr函数蕴含const被删除
#include <iostream>
struct S {
constexpr int &f();
int &f() const;
};
int main(int argc, char* argv[])
{
return 0;
}
g++ -std=c++11 -o n3652 n3652.cpp //compile failed
n3652.cpp:6:10: error: ‘int& S::f() const’ cannot be overloaded with ‘constexpr int& S::f() const’
6 | int &f() const;
| ^
n3652.cpp:5:20: note: previous declaration ‘constexpr int& S::f() const’
5 | constexpr int &f();
| ^
g++ -std=c++14 -o n3652 n3652.cpp // compile ok