优秀的编程知识分享平台

网站首页 > 技术文章 正文

C++14语言特性之三:对constexpr函数限制放宽

nanyue 2024-07-23 13:48:04 技术文章 14 ℃

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的函数可以含有以下内容:

  • 任何声明,除了:staticthread_local变量;没有初始化的变量声明。
  • 条件分支语句ifswitch
  • 所有的循环语句,包括基于范围的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

Tags:

最近发表
标签列表