C++常量表达式的使用方式

 

C++98时代

C++98编译器对int常量情有独钟,因为这是少数它能直接识别的东西。因为这个有限的能力,编译器就能够预先判定数组的大小了:

TEST_METHOD(TestConstVar)
{
 //int n = 3;
 const int n = 3;
 int a[n] = { 0 };
 Assert::AreEqual(size_t(3), _countof(a));

 const int m = n * 3;
 int b[m] = { 0 };
 Assert::AreEqual(size_t(9), _countof(b));
}

并由此还引入了一个“常量折叠”的概念,即编译器会自动将所有const int变量的引用全部替换为常量:

TEST_METHOD(TestConstVarFold)
{
 const int a = 10;
 int b = 2 * a;
 int* p = (int*)&a;
 *p = 100;

 // 没有常量折叠?
 Assert::AreEqual(100, a);
 Assert::AreEqual(20, b);
 Assert::AreEqual(100, *p);
}

我们不必纠结于这里的a到底是10还是100,这完全取决于编译器的实现。而实际工作中谁要写出这样的代码,直接拖出去打死了事。

 

C++11时代

constexpr值

C++98编译器对常量的那点有限智商实在是令人着急。C++11干脆就引入了一个新的关键字constexpr,以便让编译器可以做更多的事情。

TEST_METHOD(TestConstExprVar)
{
 constexpr int n = 3;
 int a[n] = { 0 };
 Assert::AreEqual(size_t(3), _countof(a));

 constexpr int m = n * 3;
 int b[m] = { 0 };
 Assert::AreEqual(size_t(9), _countof(b));
}

constexpr看起来和const没啥区别嘛?但实际上,你可以把constexpr理解为真正的编译期常量,而const实际上是运行期常量,以前之所以能在编译期起作用完全是不得已的救场客串行为。

constexpr函数

当然,如果constexpr仅仅有这点作用,那是绝对不会被作为新的关键字引入的。更为重要的是,既然编译期已经知道constexpr就代表编译期可以运行的东西,那么它为什么不可以修饰函数?让只能在运行期调用的函数可以在编译期起作用:

static constexpr int size()
{
 return 3;
}

static constexpr int sqrt(int n)
{
 return n * n;
}

static constexpr int sum(int n)
{
 return n > 0 ? n + sum(n - 1) : 0;
}
TEST_METHOD(TestConstExprFunc)
{
 int a[size()] = { 0 };
 Assert::AreEqual(size_t(3), _countof(a));

 int b[sqrt(3)] = { 0 };
 Assert::AreEqual(size_t(9), _countof(b));

 int c[sum(3)] = { 0 };
 Assert::AreEqual(size_t(6), _countof(c));
}

当然,在C++11阶段,这种constexpr函数限制很多:

  • 函数必须返回一个值,不能是void
  • 函数体只能有一条语句return
  • 函数调用前必须被定义
  • 函数必须用constexpr声明

浮点型常量

尽管有些限制,但是毕竟也是个函数,所以要实现C++98编译期头疼的浮点型常量也变得很简单了:

static constexpr double pi()
{
 return 3.1415926535897;
}

TEST_METHOD(TestConstExprDouble)
{
 int a[(int)pi()] = { 0 };
 Assert::AreEqual(size_t(3), _countof(a));
}

constexpr类

C++的一大特点就是面向对象的,既然constexpr可以修饰函数了,那为什么不能修饰成员函数呢?

class N
{
private:
 int m_n;

public:
 constexpr N(int n = 0)
  :m_n(n)
 {
 }

 constexpr int getN() const
 {
  return m_n;
 }
};

TEST_METHOD(TestConstExprConstruct)
{
 constexpr N n(3);
 int a[n.getN()] = { 0 };
 Assert::AreEqual(size_t(3), _countof(a));
}

 

C++14时代

C++11的constexpr很好,很强大。但是最为令人诟病的就是constexpr函数限制实在是太多了。于是C++14开始为其松绑:

static constexpr int abs(int n)
{
 if (n > 0)
 {
  return n;
 }
 else
 {
  return -n;
 }
}

static constexpr int sumFor(int n)
{
 int s = 0;
 for (int i = 1; i <= n; i++)
 {
  s += i;
 }

 return s;
}

static constexpr int next(int n)
{
 return ++n;
}

TEST_METHOD(TestConstExprFunc14)
{
 int a[abs(-3)] = { 0 };
 Assert::AreEqual(size_t(3), _countof(a));

 int b[sumFor(3)] = { 0 };
 Assert::AreEqual(size_t(6), _countof(b));

 int c[next(3)] = { 0 };
 Assert::AreEqual(size_t(4), _countof(c));
}

基本上,这基本上就是真正的函数了,不再限制为只能一行代码了:

  • 可以使用分支控制语句了
  • 可以使用循环控制语句了
  • 可以修改生命周期和常量表达式相同的变量了,所以连++n之类的表达式也可以支持了

甚至连函数必须返回一个值,不能是void的限制也被取消了,所以可以写setN之类的函数了,不过这个不太常用。

 

C++17时代

C++17进一步把constexpr的范围扩展到了lambda表达式:

static constexpr int lambda(int n)
{
 return [](int n) { return ++n; }(n);
}

TEST_METHOD(TestConstExprLambda)
{
 int a[lambda(3)] = { 0 };
 Assert::AreEqual(size_t(4), _countof(a));
}

为了让一个函数可以适应更多的情况,C++17还把黑手伸向了if语句,引入了所谓的“if constexpr”:

template<typename T>
static bool is_same_value(T a, T b)
{
 if constexpr (std::is_same<T, double>::value)
 {
  if (std::abs(a - b) < 0.0001)
  {
   return true;
  }
  else
  {
   return false;
  }
 }
 else
 {
  return a == b;
 }
}

TEST_METHOD(TestConstExprIf)
{
 Assert::AreEqual(false, is_same_value(5.6, 5.11));
 Assert::AreEqual(true, is_same_value(5.6, 5.60000001));
 Assert::AreEqual(true, is_same_value(5, 5));
}

以前,类似的代码需要一个模板函数加上一个特化函数,现在一个函数就搞定了,真好。

 

C++20时代

不出意料,C++20继续把黑手伸向更多的地方

constexpr和异常:

static constexpr int funcTry(int n)
{
 try
 {
  if (n % 2 == 0)
  {
   return n / 2;
  }
  else
  {
   return n;
  }
 }
 catch (...)
 {
  return 3;
 }
}

TEST_METHOD(TestConstExprTry)
{
 int a[funcTry(6)] = { 0 };
 Assert::AreEqual(size_t(3), _countof(a));

 int b[funcTry(3)] = { 0 };
 Assert::AreEqual(size_t(3), _countof(b));
}

constexpr和union:

union F
{
 int i;
 double f;
};

static constexpr int funcUnion(int n)
{
 F f;
 f.i = 3;
 f.f = 3.14;

 return n;
}

TEST_METHOD(TestConstExprUnion)
{
 int a[funcUnion(3)] = { 0 };
 Assert::AreEqual(size_t(3), _countof(a));
}

constexpr和虚函数

这个有点过分,不知道有多少实际用处,略。

立即函数

用consteval修饰的函数,表示在编译期可以立即执行,如果执行不了就报错。

static consteval int sqr(int n)
{
 return n * n;
}

TEST_METHOD(TestConstEval)
{
 int a[sqr(3)] = { 0 };
 Assert::AreEqual(size_t(9), _countof(a));
}

感知常量环境

这个有点意思,如果可以感知是否是常量环境,就可以让一个函数分别给出编译期的实现和运行期的实现,其方法是使用std::is_constant_evaluated():

static constexpr double power(double b, int n)
{
 if (std::is_constant_evaluated() && n >= 0)
 {
  double r = 1.0, p = b;
  unsigned u = unsigned(n);
  while (u != 0)
  {
   if (u & 1) r *= p;
   u /= 2;
   p *= p;
  }

  return r;
 }
 else
 {
  return std::pow(b, double(n));
 }
}

TEST_METHOD(TestConstEvaluated)
{
 constexpr double p = power(3, 2);
 Assert::AreEqual(9.0, p, 0.001);
 int m = 2;
 Assert::AreEqual(9.0, power(3, m), 0.001);
}

 

参考资料

《现代C++语言核心特性解析》

关于C++11-20 常量表达式的使用的文章就介绍至此,更多相关C++ 常量表达式内容请搜索编程宝库以前的文章,希望大家多多支持编程宝库

本文为大家分享了C++ OpenCV绘制几何图形的具体代码,供大家参考,具体内容如下绘制几何图形 直线 矩形 多边形 圆形 椭圆 文字 API直线CV_EXPORTS_W void line( ...