关于C++中constexpr的说明
一 、为什么需要constexpr
有时候需要编译时常量,现在能想到的典型的场景是在确定一个数组长度的声明中。比方说,需要64个bit,也就是8个字节,在32位机器上,需要两个long,在64位机器上需要一个long。那么此时的声明大概可能是
long bits[sizeof(long) == 32 ? 2 : 1]
这么写当然可以,但是如果多个地方都会用到的话就看起来不太舒服了,需要定义为宏。但是很多人又不建议用宏,那有没有办法封装为一个函数呢?这个时候就可以用到这种constexpr表达式。它的作用就是名字的意义const expression。和const关键字不同,constexpr表示的是这个修饰的变量在输入(如果有输入的话)是常量的时候能够在编译时获得一个输出常量,而const更多的意思是这个变量本身在运行时不能被修改。
加这个constexp的作用其实也是告诉编译器自己定义这个变量的意图(intent),从而让编译器进行额外检查,而这种额外的检查又保证了这个修饰符本身的语义,所以它更多的是为了提高代码的可读性和可维护性(不要假设代码只是给写作者一个人维护的)。constexpr就要求编译器在编译时检测这个表达式本身是不是编译时可以计算出常量值的。
看一个简单的例子
tsecer@harry: cat -n constexpr.demo.cpp
1 int foo();
2 int g;
3 constexpr int bar();
4 constexpr int gc;
5
6 constexpr int baz(int x)
7 {
8 return x + g + bar() + foo() + gc;
9 }
tsecer@harry: cc1plus -std=c++11 constexpr.demo.cpp
constexpr.demo.cpp:4:15: error: uninitialized const ‘gc’ [-fpermissive]
constexpr int gc;
^
constexpr int baz(int)
constexpr.demo.cpp:9:1: error: the value of ‘g’ is not usable in a constant expression
}
^
constexpr.demo.cpp:2:5: note: ‘int g’ is not const
int g;
^
constexpr.demo.cpp: At global scope:
constexpr.demo.cpp:3:15: warning: inline function ‘constexpr int bar()’ used but never defined [enabled by default]
constexpr int bar();
^
可以看到,表示为constexpr的函数中可以使用函数的参数,并且可以使用其他为const类型的函数以及变量。
二、gcc对于constexpr函数的检查
其主要检查在potential_constant_expression_1函数中完成,这个函数根据表达式类型递归检查,其中比较感兴趣的类型:函数参数为PARM_DECL类型,可以看到是直接返回true的,也就是前面例子中的x变量;对于g、gc属于VAR_DECL类型,而bar()属于CALL_EXPR类型,通过该case最后的return true返回满足条件(这里补充一点,“bar()”这个是函数调用类型CALL_EXPR,而单独的“bar”是FUNCTION_DECL类型)。从代码中还可看到,会对函数调用的各个参数,二元操作的操作符会递归执行该函数。
gcc-4.8.2\gcc\cp\semantics.c
static bool
potential_constant_expression_1 (tree t, bool want_rval, tsubst_flags_t flags)
{
enum { any = false, rval = true };
int i;
tree tmp;
if (t == error_mark_node)
return false;
if (t == NULL_TREE)
return true;
if (TREE_THIS_VOLATILE (t))
{
if (flags & tf_error)
error ("expression %qE has side-effects", t);
return false;
}
if (CONSTANT_CLASS_P (t))
return true;
switch (TREE_CODE (t))
{
case FUNCTION_DECL:
case BASELINK:
case TEMPLATE_DECL:
case OVERLOAD:
case TEMPLATE_ID_EXPR:
case LABEL_DECL:
case CONST_DECL:
case SIZEOF_EXPR:
case ALIGNOF_EXPR:
case OFFSETOF_EXPR:
case NOEXCEPT_EXPR:
case TEMPLATE_PARM_INDEX:
case TRAIT_EXPR:
case IDENTIFIER_NODE:
case USERDEF_LITERAL:
/* We can see a FIELD_DECL in a pointer-to-member expression. */
case FIELD_DECL:
case PARM_DECL:
case USING_DECL:
return true;
case AGGR_INIT_EXPR:
case CALL_EXPR:
/* -- an invocation of a function other than a constexpr function
or a constexpr constructor. */
{
tree fun = get_function_named_in_call (t);
const int nargs = call_expr_nargs (t);
i = 0;
if (is_overloaded_fn (fun))
{
if (TREE_CODE (fun) == FUNCTION_DECL)
{
if (builtin_valid_in_constant_expr_p (fun))
return true;
if (!DECL_DECLARED_CONSTEXPR_P (fun)
/* Allow any built-in function; if the expansion
isn't constant, we'll deal with that then. */
&& !is_builtin_fn (fun))
{
if (flags & tf_error)
{
error_at (EXPR_LOC_OR_HERE (t),
"call to non-constexpr function %qD", fun);
explain_invalid_constexpr_fn (fun);
}
return false;
}
/* A call to a non-static member function takes the address
of the object as the first argument. But in a constant
expression the address will be folded away, so look
through it now. */
if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fun)
&& !DECL_CONSTRUCTOR_P (fun))
{
tree x = get_nth_callarg (t, 0);
if (is_this_parameter (x))
{
if (DECL_CONSTRUCTOR_P (DECL_CONTEXT (x)))
{
if (flags & tf_error)
sorry ("calling a member function of the "
"object being constructed in a constant "
"expression");
return false;
}
/* Otherwise OK. */;
}
else if (!potential_constant_expression_1 (x, rval, flags))
return false;
i = 1;
}
}
else
{
if (!potential_constant_expression_1 (fun, true, flags))
return false;
fun = get_first_fn (fun);
}
/* Skip initial arguments to base constructors. */
if (DECL_BASE_CONSTRUCTOR_P (fun))
i = num_artificial_parms_for (fun);
fun = DECL_ORIGIN (fun);
}
else
{
if (potential_constant_expression_1 (fun, rval, flags))
/* Might end up being a constant function pointer. */;
else
return false;
}
for (; i < nargs; ++i)
{
tree x = get_nth_callarg (t, i);
if (!potential_constant_expression_1 (x, rval, flags))
return false;
}
return true;
}
……
case VAR_DECL:
if (want_rval && !decl_constant_var_p (t)
&& !dependent_type_p (TREE_TYPE (t)))
{
if (flags & tf_error)
non_const_var_error (t);
return false;
}
return true;
……
}
三、常量的展开
主要也是递归执行常量展开处理
/* Attempt to reduce the expression T to a constant value.
On failure, issue diagnostic and return error_mark_node. */
/* FIXME unify with c_fully_fold */
static tree
cxx_eval_constant_expression (const constexpr_call *call, tree t,
bool allow_non_constant, bool addr,
bool *non_constant_p, bool *overflow_p)
{
……
switch (TREE_CODE (t))
{
case VAR_DECL:
if (addr)
return t;
/* else fall through. */
case CONST_DECL:
r = integral_constant_value (t);
if (TREE_CODE (r) == TARGET_EXPR
&& TREE_CODE (TARGET_EXPR_INITIAL (r)) == CONSTRUCTOR)
r = TARGET_EXPR_INITIAL (r);
if (DECL_P (r))
{
if (!allow_non_constant)
non_const_var_error (r);
*non_constant_p = true;
}
break;
case FUNCTION_DECL:
case TEMPLATE_DECL:
case LABEL_DECL:
return t;
case PARM_DECL:
if (call && DECL_CONTEXT (t) == call->fundef->decl)
{
if (DECL_ARTIFICIAL (t) && DECL_CONSTRUCTOR_P (DECL_CONTEXT (t)))
{
if (!allow_non_constant)
sorry ("use of the value of the object being constructed "
"in a constant expression");
*non_constant_p = true;
}
else
r = lookup_parameter_binding (call, t);
}
else if (addr)
/* Defer in case this is only used for its type. */;
else
{
if (!allow_non_constant)
error ("%qE is not a constant expression", t);
*non_constant_p = true;
}
break;
case CALL_EXPR:
case AGGR_INIT_EXPR:
r = cxx_eval_call_expression (call, t, allow_non_constant, addr,
non_constant_p, overflow_p);
break;
……
case ADDR_EXPR:
{
tree oldop = TREE_OPERAND (t, 0);
tree op = cxx_eval_constant_expression (call, oldop,
allow_non_constant,
/*addr*/true,
non_constant_p, overflow_p);
/* Don't VERIFY_CONSTANT here. */
if (*non_constant_p)
return t;
/* This function does more aggressive folding than fold itself. */
r = build_fold_addr_expr_with_type (op, TREE_TYPE (t));
if (TREE_CODE (r) == ADDR_EXPR && TREE_OPERAND (r, 0) == oldop)
return t;
break;
}
……
}
四、关于constexpr意义的讨论
这里又有个说人话的说明
constexpr: compile time if you can
A constant expression doesn't mean "compile time expression" or "constant value" or "constant function".
A non-formal description of a constant expression could be:
Given compile time input, the constant expression can compute the result.
For a variable, it's pretty straightforward. You set the value at compile time, you have the result at compile time.
In C++ 14 (and also C++ 11 actually, just with more restrictions), the constexpr can also be applied to a function or method. What does that mean?
A constexpr function is able to compute its result at compilation time, if its input is known at compilation time.
In other words, any function that has "everything it needs" to compute its result at compile-time, can be a constant expression.
五、关于FUNCTION_DECL的一个有趣的例子
可以看到,这个addr返回的并不是“编译时”常量,而是一个链接是常量。可以通过编译的原因在于foo本身是一个FUNCTION_DECL类型,而在potential_constant_expression_1检查中对于这种类型的case是直接返回true的。
tsecer@harry: cat -n constexpr.func.cpp
1 typedef int (*FUN)();
2 int foo();
3 constexpr FUN addr()
4 {
5 return foo;
6 }
tsecer@harry: cc1plus -std=c++11 constexpr.func.cpp
tsecer@harry: