Можно ли узнать, когда constexpr действительно является constexpr?
Начиная с расширенных версий 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), выполняется ли функция во время компиляции или во время выполнения?
РЕДАКТИРОВАТЬ: Также возможно ли знать во время выполнения, была ли функция оценена во время компиляции?
8 ответов
Перечисленная техника работает, но так как она использует static_assert
это не сфинае дружелюбно. Лучший способ (теоретически вы поймете, что я имею в виду) сделать это, чтобы проверить, является ли функция noexcept
, Зачем? Потому что константные выражения всегда исключают, даже если функции не помечены как таковые. Итак, рассмотрим следующий код:
template <class T>
constexpr void test_helper(T&& 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
только noexcept
если x
является постоянным выражением, и b
правда; если логическое значение ложно, то мы называем не constexpr
функция, разрушая наше сознание. Итак, давайте проверим это:
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
всегда возвращает истину, делая тест бесполезным.
В пределах constexpr
функция, вы не можете сказать, если вы оценивались в constexpr
контекст. Было несколько предложений по добавлению этой функциональности. Ни один не преуспел.
Вне constexpr
есть несколько способов определить, будет ли выполняться вызов функции с определенным набором аргументов в constexpr
контекст. Проще всего было бы использовать результат в контексте, требующем constexpr
,
Предполагая, что ваше выражение constexpr возвращает непустой интеграл или тип указателя (включая указатель на функцию):
#define CONSTEXPR_EVAL(...) \
std::integral_constant< \
std::decay_t<decltype(__VA_ARGS__)>, \
__VA_ARGS__ \
>::value
затем CONSTEXPR_EVAL( bar(foo, true) )
не скомпилируется, если bar(foo, true)
не может быть вычислено во время компиляции, и если оно может быть оценено во время компиляции, оно возвращает это значение.
Другие трюки с участием noexcept
(функция, вычисленная во время компиляции noexcept
) может работать (см . ответ @NirFriedman).
Если вы можете использовать C++20, есть std::is_constant_evaluated
который делает именно то, что вы хотите. std::is_constant_evaluated
обычно реализуется с использованием встроенного компилятора.
Это называется __builtin_is_constant_evaluated
в GCC и clang, поэтому вы можете реализовать свою собственную "безопасную" оболочку вокруг него, даже в 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+), поэтому вы также можете определить версию компилятора.
Я думаю, что канонический способ сделать это с 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
компилирует нормально для меня, показывая, что все работает, как ожидалось.
Основываясь на информации из этого обсуждения, я создал следующий минимальный пример:
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
Извините, что испортил вечеринку, но, безусловно, не существует стандартного способа сделать это. Согласно правилу "как будто", компилятор может выдавать код, который вычисляет результат во время выполнения, даже в тех случаях, когда он уже был вынужден вычислить его во время компиляции в другом контексте. Все, что можно сделать во время компиляции, можно сделать снова во время выполнения, верно? И расчет уже доказан, чтобы не бросать.
Таким образом, любой стандарт IS_REALLY_CONSTEXPR
или же is_really_constexpr
проверка не может опровергнуть, что точно такой же вызов, или, если на то пошло, значение точно такой же constexpr
Символ включает в себя расчет времени выполнения.
Конечно, обычно нет никаких причин повторять вычисления во время выполнения, которые могут быть сделаны или даже уже сделаны во время компиляции, но вопрос был в том, чтобы сказать, использует ли компилятор предварительно вычисленный результат, и нет ни одного,
Теперь вы сказали " возможно, стандартный", поэтому, по сути, вам лучше всего протестировать одно из предложенных решений с выбранным компилятором и надеяться, что оно будет работать согласованно. (Или читая исходный код, если он открытый / общедоступный, и вы так склонны.)
В C++23 добавлен if consteval именно для этой цели.
constexpr int foo(const int s)
{
if consteval
{
// Compile time
}
else
{
// Runtime
}
}