Подтвердить что ты не робот

Constexpr во время компиляции, но не накладные расходы во время выполнения

Известный трюк, вызывающий ошибку времени компиляции при оценке функции constexpr, делая что-то вроде этого:

constexpr int f(int x) {
    return (x != 0) ? x : throw std::logic_error("Oh no!");
}

И если функция используется в контексте constexpr, вы получите ошибку времени компиляции, если x == 0. Однако, если аргумент f не является constexpr, тогда он будет генерировать исключение во время выполнения, если x == 0, что может не всегда быть желательным по соображениям производительности.

Подобно теории assert, охраняемой NDEBUG, существует ли способ вызвать ошибку времени компиляции с помощью функции constexpr, но ничего не делать во время выполнения?

Наконец, расслабьтесь constexpr правила в С++ 1y (С++ 14) что-нибудь измените?

4b9b3361

Ответ 1

Есть ли способ вызвать ошибку времени компиляции с помощью функции constexpr, но ничего не делать во время выполнения?

Вы можете использовать тот же трюк, но вместо использования выражения throw используйте выражение, которое не является постоянным выражением, но делает то, что вы хотите во время выполнения. Например:

int runtime_fallback(int x) { return x; } // note, not constexpr
constexpr int f(int x) {
  return (x != 0) ? x : runtime_fallback(0);
}

constexpr int k1 = f(1); // ok
constexpr int k2 = f(0); // error, can't call 'runtime_fallback' in constant expression
int k3 = f(0);           // ok

Устранены ли правила constexpr в С++ 1y (С++ 14) что-нибудь изменить?

Не в этой области нет. Существуют некоторые формы выражения, которые действительны в постоянных выражениях в С++ 14, но не в С++ 11, но в этом списке нет ни выражений, ни вызовов функций не constexpr.

Ответ 2

Если аргумент f не является constexpr, однако, он будет генерировать исключение во время выполнения, если x == 0, что может не всегда быть желательным по соображениям производительности.

Аргумент функции никогда не считается постоянным выражением. Для разграничения требовалось бы, чтобы объекты времени компиляции и времени выполнения имели разные типы.

Несмотря на то, что компилятор использует чистую функциональную семантику, когда оценивает функцию во время компиляции, она по-прежнему остается той же функцией с тем же значением. Если вам нужна другая функция схожих, но разных значений, вам придется либо определить другую целую функцию, либо создать шаблон.

Вы можете использовать такую ​​подпись:

template< typename int_type >
constexpr int f(int_type x);

с такими вызовами:

f( std::integral_constant< int, 0 >() ) // Error.
f( std::integral_constant< int, 3 >() ) // OK.
f( 0 ) // Not checked.

Метапрограммирование может сказать, что integral_constant означает значение времени компиляции. Но я не думаю, что это действительно уместно. Если одно чувство функции работает с нулем, а другое - нет, тогда у вас есть две разные функции.

Идиома обертки может предотвратить дублирование между различными функциями:

constexpr int f_impl(int x) { // Actual guts of the function.
    return x;
}

int f(int x) { // Non-constexpr wrapper prevents accidental compile-time use.
    assert ( x != 0 && "Zero not allowed!" );
    return f_impl( x );
}

template< int x > // This overload handles explicitly compile-time values.
constexpr int f( std::integral_constant< int, x > ) {
    static_assert ( x != 0, "Zero not allowed!" );
    return f_impl( x );
}

Ответ 3

Вместо использования функции constexpr вы должны использовать static_assert. Это позволяет запускать утверждение во время компиляции с нулевыми затратами времени выполнения.

Ответ 4

Это должно работать:

#ifdef NDEBUG
    // Suppresses unused variable warnings in release builds.
    #define ASSERT(X) (void(sizeof (X)))
#else
    #define ASSERT(X) ((X) ? void() : std::abort())
#endif

constexpr int f(int const x)
{
    return ASSERT(x != 0), x;
}

Здесь вы можете увидеть выход здесь. Если вы добавите constexpr в начало строки 17, вместо этого вы получите ошибку времени компиляции.

Ответ 5

Это похоже на трюк. Это не очень красиво, но идея состоит в том, чтобы различать значение, доступное во время компиляции, из того, которое не использует SFINAE для constexprs. Я могу скомпилировать это с clang 3.3 и не выполнить компиляцию, когда я пытаюсь использовать f (0) в контексте constexpr, но не бросаю, когда использую его во время выполнения. Вероятно, вы можете создать однопараметрическую перегрузку maybethrow, которая делает предпочтительный трюк (not_) для его реализации.

struct not_preferred {};
struct preferred { operator not_preferred() { return not_preferred(); } };

template< typename T, T X >
T compiletime() { return X; }

template< typename T >
constexpr auto maybethrow( preferred, T x ) -> decltype( compiletime< T, x >() )
{
    return 0 ? x : throw 1;
}

template< typename T >
constexpr auto maybethrow( not_preferred, T x ) -> T
{
    return x;
}

constexpr int f(int x)
{
    return x ? x + 1 : maybethrow( preferred(), x + 1 );
}