[英]Consteval constructor and member function calls in constexpr functions
[英]New-expression with consteval constructor in constexpr context
struct A {
consteval A() {};
};
constexpr bool g() {
auto a = new A;
delete a;
return true;
}
int main() {
static_assert(g());
}
https://godbolt.org/z/jsq35WxKs
GCC 和 MSVC 拒絕該程序,ICC 和 Clang 接受它:
///MSVC:
<source>(6): error C7595: 'A::A': call to immediate function is not a constant expression
Compiler returned: 2
//GCC:
<source>: In function 'constexpr bool g()':
<source>:6:18: error: the value of '<anonymous>' is not usable in a constant expression
6 | auto a = new A;
| ^
<source>:6:18: note: '<anonymous>' was not declared 'constexpr'
<source>:7:12: error: type '<type error>' argument given to 'delete', expected pointer
7 | delete a;
| ^
Compiler returned: 1
雖然,用 new new A()
替換new A
會導致 GCC 也接受該程序(但new A{}
也不會)。
至少進行以下更改之一會導致所有四個編譯器都接受該程序:
用constexpr
替換consteval
將constexpr
替換為consteval
代替
auto a = new A; delete a;
和
auto alloc = std::allocator<A>{}; auto a = alloc.allocate(1); std::construct_at(a); std::destroy_at(a); alloc.deallocate(a, 1);
與A a;
, auto&& a = A{};
或使用A{};
只有例外:
帶有 libstdc++ 的 Clang trunk 似乎由於不相關的錯誤而無法使用std::allocator
版本進行編譯。 對於 Clang 13 或 libc++,它也被接受。
In file included from <source>:1: In file included from [...]/memory:78: [...]/shared_ptr_atomic.h:459:14: error: missing 'typename' prior to dependent type name '_Atomic_count::pointer' static _Atomic_count::pointer
只要構造函數上有consteval
,MSVC 就會拒絕std::allocator
版本:
error C7595: 'A::A': call to immediate function is not a constant expression <source>(10): note: see reference to function template instantiation '_Ty *std::construct_at<_Ty,,void>(_Ty *const ) noexcept(false)' being compiled with [ _Ty=A ]
替換static_assert(g());
使用g()
或完全刪除調用似乎對這些結果沒有任何影響。
哪些編譯器是正確的,如果原始編譯器格式錯誤,為什么只不允許限定符和構造方法的特定組合?
受此答案下評論的啟發。
相關的措辭是[expr.const]/13 :
如果表達式或轉換是立即函數的潛在評估顯式或隱式調用並且不在立即函數上下文中,則它是立即調用。 立即調用應是常量表達式。
請注意“或轉換”和“隱式調用”這兩個詞——這似乎暗示該規則旨在應用於每個函數調用的基礎上。 1單個原子表達式的求值可以包含多個這樣的調用,例如new 表達式可以調用分配函數、構造函數和釋放函數的情況。 如果選擇的構造函數是consteval
,則初始化對象的new 表達式的求值部分(即構造函數調用),並且只有該部分是立即調用。 在這種解釋下,無論上下文如何,將new
與consteval
構造函數一起使用都不應該是格式錯誤的——即使在常量表達式之外——當然,只要對象的初始化本身是常量。
然而,這種解讀存在一個問題:最后一句話清楚地表明立即調用必須是一個表達式。 上面描述的“子原子調用”不是一個,它沒有值類別,並且不可能滿足常量表達式的定義( [expr.const]/11 ):
常量表達式是指一個實體的左值核心常量表達式,該實體是常量表達式(定義如下)的允許結果,或者是一個純右值核心常量表達式,其值滿足以下約束 [...]
對該措辭的字面解釋將排除在直接函數上下文之外使用任何consteval
構造函數,因為對它的調用永遠不會作為獨立表達式出現。 這顯然不是預期的含義 - 除其他外,它會使部分標准庫無法使用。
這種閱讀的更樂觀(但也不太忠實於所寫的文字)版本是包含調用的原子表達式(形式上:調用是2的直接子表達式的表達式)必須是常量表達式。 這仍然不允許您new A
構造,因為它本身不是常量表達式,並且在函數參數或一般變量的初始化等情況下也會留下一些不確定性。
我傾向於相信第一次閱讀是預期的, new A
應該沒問題,但顯然存在實施分歧。
至於自相矛盾的“應該是常量表達式”要求,這並不是標准中唯一出現這種情況的地方。 在同一部分的前面, [expr.const]/2.2 :
變量或臨時對象 o 是常量初始化的,如果 [...]
- 當解釋為常量表達式時,其初始化的完整表達式是常量表達式[...]
顯然,以下內容應該是有效的:
constinit A a;
但看不到固定的表情。
所以,回答你的問題:
對g
的調用是否作為明顯常量求值表達式的一部分求值並不重要3無論您使用哪種 [expr.const]/13 解釋。 new A
即使在正常評估期間也是良構的,或者在直接函數上下文之外的任何地方都是良構的。
從表面上看,Clang 和 ICC 執行的是前一套規則,而 GCC 和 MSVC 遵循的是后者。 除了 GCC 接受new A()
作為異常值(這顯然是一個錯誤)之外,兩者都沒有錯,措辭只是有缺陷。
[1] CWG2410修復了措辭以正確包含諸如構造函數調用之類的內容(既不是表達式也不是轉換)。
[2] 是的,非表達式可以是子表達式。
[3] 這樣的要求是不可能執行的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.