[英]Is it possible to test if a constexpr function is evaluated at compile time?
由於constexpr
的擴展版本(我認為來自 C++14),您可以聲明可用作“真實” constexpr
constexpr
函數。 也就是說,代碼在編譯時執行或可以作為內聯函數運行。 那么什么時候可以有這個程序:
#include <iostream>
constexpr int foo(const int s) {
return s + 4;
}
int main()
{
std::cout << foo(3) << std::endl;
const int bar = 3;
std::cout << foo(bar) << std::endl;
constexpr int a = 3;
std::cout << foo(a) << std::endl;
return 0;
}
結果是:
7
7
7
到現在為止還挺好。
有沒有辦法(可能是標准的)在foo(const int s)
知道函數是在編譯時還是在運行時執行?
編輯:是否有可能在運行時知道函數是否在編譯時被評估?
列出的技術有效,但由於它使用static_assert
因此它不是 sfinae 友好的。 執行此操作的更好方法(理論上,您會明白我的意思)是檢查函數是否為noexcept
。 為什么? 因為,常量表達式總是 noexcept,即使函數沒有被標記為這樣。 因此,請考慮以下代碼:
template <class T>
constexpr void test_helper(T&&) {}
#define IS_CONSTEXPR(...) noexcept(test_helper(__VA_ARGS__))
test_helper
是constexpr
,所以只要它的參數是,它就會是一個常量表達式。 如果它是一個常量表達式,它將是noexcept
,否則它不會是(因為它沒有被標記為這樣)。
所以現在讓我們定義這個:
double bar(double x) { return x; }
constexpr double foo(double x, bool b) {
if (b) return x;
else return bar(x);
}
foo
僅在x
是常量表達式且b
為真時才為noexcept
; 如果布爾值是假的,那么我們調用一個非constexpr
函數,破壞我們的 constexpr-ness。 所以,讓我們測試一下:
double d = 0.0;
constexpr auto x = IS_CONSTEXPR(foo(3.0, true));
constexpr auto y = IS_CONSTEXPR(foo(3.0, false));
constexpr auto z = IS_CONSTEXPR(foo(d, true));
std::cerr << x << y << z;
編譯通過,很棒! 這為我們提供了編譯時布爾值(不是編譯失敗),例如可用於 sfinae。
漁獲? 嗯,clang 有一個多年的錯誤,並且沒有正確處理這個問題。 然而,gcc 確實如此。 現場示例: http : //coliru.stacked-crooked.com/a/e7b037932c358149 。 它應該打印“100”。
C++20 引入了is_constant_evaluated
,在頭文件<type_traits>
定義,它解決了這個問題。
constexpr int foo(int s)
{
if (std::is_constant_evaluated()) // note: not "if constexpr"
/* evaluated at compile time */;
else
/* evaluated at run time */;
}
請注意,這里使用的是普通的if
而不是if constexpr
。 如果使用if constexpr
,則必須在編譯時評估條件,因此is_constant_evaluated
始終返回 true,從而使測試無用。
我認為這樣做的規范方法是使用static_assert
。 static_assert
在編譯時進行評估,因此如果它們的條件為假,它們將破壞構建。
#include <iostream>
constexpr int foo(const int s) {
return s + 4;
}
int main()
{
std::cout << foo(3) << std::endl;
const int bar = 3;
std::cout << foo(bar) << std::endl;
constexpr int a = 3;
std::cout << foo(a) << std::endl;
static_assert(foo(3) == 7, "Literal failed");
static_assert(foo(bar) == 7, "const int failed");
static_assert(foo(a) == 7, "constexpr int failed");
return 0;
}
clang++ -std=c++14 so1.cpp
對我來說編譯得很好,表明一切都按預期工作。
在constexpr
函數中,您可以判斷您是否在c++20之前的constexpr
上下文中被評估。 從c++20 開始, 添加了此功能—— constexpr bool std::is_constant_evaluated()
會告訴您是否在constexpr
上下文中被調用。
在constexpr
函數之外,有多種方法可以確定對具有特定參數集的函數的調用是否會在constexpr
上下文中進行評估。 最簡單的方法是在需要constexpr
的上下文中使用結果。
假設您的 constexpr 表達式返回一個非空整數或指針類型(包括函數指針):
#define CONSTEXPR_EVAL(...) \
std::integral_constant< \
std::decay_t<decltype(__VA_ARGS__)>, \
__VA_ARGS__ \
>::value
如果bar(foo, true)
不能在編譯時求值,則CONSTEXPR_EVAL( bar(foo, true) )
將無法編譯,如果可以在編譯時求值,則返回該值。
其他涉及noexcept
技巧(在編譯時評估的函數是noexcept
)可以工作(請參閱@NirFriedman 的回答)。
如果您可以使用 C++20,那么std::is_constant_evaluated
可以完全滿足您的需求。 std::is_constant_evaluated
通常使用編譯器內部實現。
這在 GCC 和 clang 中稱為__builtin_is_constant_evaluated
,因此您可以圍繞它實現自己的“安全”包裝器,即使在 C++17 及更低版本中也是如此。
// if C++20, we will need a <type_traits> include for std::is_constant_evaluated
#if __cplusplus >= 202002L
#include <type_traits>
#endif
constexpr bool is_constant_evaluated() {
#if __cplusplus >= 202002L
return std::is_constant_evaluated();
#elif defined(__GNUC__) // defined for both GCC and clang
return __builtin_is_constant_evaluated();
#else
// If the builtin is not available, return a pessimistic result.
// This way callers will implement everything in a constexpr way.
return true;
#endif
}
請注意,此內置函數仍然相對較新(GCC 9.0+),因此您可能還想檢測編譯器版本。
根據本次討論中的信息,我制作了以下最小示例:
template <class T>
constexpr void test_helper(T &&) {}
#define IS_CONSTEXPR(...) noexcept(test_helper(__VA_ARGS__))
constexpr void test(){
static_assert(IS_CONSTEXPR(10), "asdfadsf");
constexpr const int x = 10;
static_assert(IS_CONSTEXPR(x), "asdfadsf");
int y;
static_assert(IS_CONSTEXPR(y), "asdfadsf");
}
int main(){
test();
return 0;
}
令我失望的是,它無法在每個 static_asserts 上編譯。 見https://www.godbolt.org/z/Tr3z93M3s
抱歉破壞了派對,但肯定沒有標准的方法來做到這一點。 在 as-if 規則下,編譯器可以發出在運行時計算結果的代碼,即使在編譯時已經被迫在不同上下文中計算結果的情況下也是如此。 任何可以在編譯時完成的事情都可以在運行時再次完成,對嗎? 並且計算已經被證明不會拋出。
因此,通過擴展,任何符合標准的IS_REALLY_CONSTEXPR
或is_really_constexpr
檢查都不能反駁完全相同的調用,或者就此而言,完全相同的constexpr
符號的值涉及運行時計算。
當然,通常沒有任何理由在運行時重復可以完成甚至在編譯時已經完成的計算,但問題是關於告訴編譯器是否使用預先計算的結果,並且沒有.
現在您確實說可能是標准的,因此實際上您最好的選擇可能是使用您選擇的編譯器測試提供的解決方案之一,並希望它的行為一致。 (或者閱讀源代碼,如果它是開放/公共源代碼並且你很喜歡。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.