Можно ли узнать, когда 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
    }
}
Другие вопросы по тегам