const 表示的只是这个变量不可修改,但并未限定这个变量是编译期常量还是运行期常量;而 constexpr 只能是编译期常量。
const 修饰的函数一般都是成员函数,用来表示这个函数不会对成员变量产生写操作,这点很好理解。
constexpr 修饰的函数,简单的来说,如果其传入的参数可以在编译时期计算出来,那么这个函数就会产生编译时期的值。但是,传入的参数如果不能在编译时期计算出来,那么 constexpr 修饰的函数就和普通函数一样了,比如上面代码直接调用了 func(i)
。不过,我们不必因此而写两个版本,所以如果函数体适用于 constexpr 函数的条件,可以尽量加上
}
在C++
中,编译时和运行时之间的边界是模糊的,这在C++14
中引入泛化常量表达式时更是如此。 然而,能够操纵异构对象就意味着要能深刻理解边界的含义,让代码按自己的意图来运行。 本节的目标是使用constexpr
来设置一些东西; 以了解哪些问题可以解决,哪些不能。 本节涵盖了关于常量表达式的高级概念;
只有对constexpr
有很好理解的读者才应该尝试阅读。
让我们开始一个具有挑战性的问题。 下面的代码可编译吗?
答案是不能,由Clang
给出的错误就像:
对出错的解释是,在f
的函数体内,t
不是常数表达式,因此不能用作static_assert
的操作数。 原因是这样的函数根本不能由编译器生成。 要理解这个问题,考虑当我们使用具体类型实例化f
模板时发生了什么:
显然,编译器不能生成f<int>
的代码,如果t!= 1
,它应该触发一个static_assert
,因为我们还没有指定t
的值。 更糟的是,生成的函数应该适用于常量和非常量表达式:
显然,不能生成fptr
的代码,因为它需要能够对运行时值进行static_assert
,这是没有意义的。 此外,注意,无论你是否使用constexpr
函数都没关系; 使f
constexpr
只声明f
的结果是一个常量表达式,只要它的参数是一个常量表达式,但它仍然不能让你知道你是否使用f
的body
中的常量表达式调用。 换句话说,我们想要的是:
在这个假设情况下,编译器将知道t
是来自f
的主体的常量表达式,并且可以使static_asser
t起作用。 然而,当前语言还不constexpr
参数,并且添加它们将带来非常具有挑战性的设计和实现问题。
这个小实验的结论是参数传递剥离了constexpr-ness。 现在可能不清楚的是这种剥离的后果,接下来解释。
参数不是常量表达式意味着我们不能将其用作非类型模板参数,数组绑定,static_assert
或需要常量表达式的任何其他地方。 此外,这意味着函数的返回类型不能取决于参数的值,如果你想以这样的形式得到一个新类型:
显然,这行不通。事实上,函数的返回类型只能取决于它的参数的类型,而constexpr
不能改变这个事实。 但根据函数的参数返回具有不同类型的对象对我们至关重要,因为我们对操作异构对象感兴趣。 例如,一个函数可能希望在一种情况下返回类型T
的对象,在另一种情况下返回类型U
的对象;
从以上分析来看,我们现在知道这些“情况”将必须依赖于参数类型编码的信息,而不是它们的值。
为了通过参数传递来保留constexpr
,我们必须将constexpr
值编码为一个类型,然后将一个不一定是该类型的constexpr
对象传递给函数。 该函数必须是模板,然后可以访问在该类型内编码的constexpr
值。
TODO: 改进这个解释,并谈论包装成类型的非整数常量表达式。
让我提一个棘手的问题。 以下代码是否有效?
答案是肯定的,但原因可能不明显。 这里发生的是,我们有一个非常量的值n
和一个constexpr
函数f
和它的引用参数。 大多数人认为它不应该工作的原因是n
不是constexpr
。
但是,我们不在f
内部做任何事情,所以没有什么实质的理由解释它不应该工作! 这有点像在内部的一个constexpr
函数:
只要throw
出现的代码路径不被执行,调用的结果可以是常量表达式。 同样,我们可以在f
中做任何我们想要的事,只要我们不执行需要访问它的参数n
的代码,因为这不是一个常量表达式:
Clang
给出的第二次调用的错误是:
让我们现在停下来看看它的游戏规则,并考虑一个更微妙的例子。 以下代码是否有效?
与我们的初始场景唯一的区别是,f
现在的参数按值而不是按引用传递。 然而,这与上一个函数有所不同。 事实上,我们现在要求编译器创建一个n
的副本,并将此副本传递给f
。 然而,n
不是constexpr
,所以它的值只在运行时知道。
编译器要怎么操作编译一个变量的副本(在编译时),但该变量的值只在运行时才知道? 当然,它不能。 事实上,Clang
给出的错误信息对于发生了什么很清楚:
TODO: 解释在常量表达式中不会出现副作用,即使它们产生的表达式不被访问。
}