關於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:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM