Возможно ли is_constexpr в C++11?
Можно ли создать логическое значение времени компиляции на основе того, является ли выражение C++11 постоянным выражением (т. Е. constexpr
) в C++11? Несколько вопросов по SO относятся к этому, но я нигде не вижу прямого ответа.
6 ответов
По состоянию на 2017 год is_constexpr
невозможно в C++11. Звучит странно, поэтому позвольте мне немного рассказать об истории.
Сначала мы добавили эту функцию для устранения дефекта: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html
Йоханнес Шауб - litb опубликовал макрос обнаружения constexpr, который опирался на положение о том, что константные выражения неявно отсутствуют. Это работало в C++11, но никогда не было реализовано по крайней мере некоторыми компиляторами (например, clang). Затем в рамках C++17 мы оценили удаление устаревших спецификаций исключений из C++17. Как побочный эффект этой формулировки, мы случайно удалили это положение. Когда основная рабочая группа обсудила вопрос о добавлении этого положения, они поняли, что с этим возникли серьезные проблемы. Вы можете увидеть полную информацию в отчете об ошибках LLVM. Поэтому вместо того, чтобы добавлять его обратно, мы решили считать его дефектом всех версий стандарта и удалили его задним числом.
В результате этого, насколько мне известно, нет способа определить, можно ли использовать выражение как константное выражение.
Я однажды написал это (РЕДАКТИРОВАТЬ: см. Ниже для ограничений и объяснений). С /questions/1303886/constexpr-staticassert-i-vstraivanie/1303907#1303907:
template<typename T>
constexpr typename remove_reference<T>::type makeprval(T && t) {
return t;
}
#define isprvalconstexpr(e) noexcept(makeprval(e))
Однако существует много видов постоянных выражений. Приведенный выше ответ обнаруживает постоянные выражения prvalue.
объяснение
noexcept(e)
выражение дает false
тогда и только тогда e
содержит
- потенциально вычисляемый вызов функции, которая не имеет спецификации исключения без выброса, если только вызов не является константным выражением,
- потенциально оцененный
throw
выражение, - потенциально оцениваемая бросаемая форма
dynamic_cast
или жеtypeid
,
Обратите внимание, что шаблон функции makeprval
не объявлено noexcept
таким образом, вызов должен быть постоянным выражением, чтобы первый пункт не применялся, и это то, что мы злоупотребляем. Нам нужны другие пули, чтобы не применять, а к счастью, оба throw
и бросаемый dynamic_cast
или же typeid
не допускаются в константных выражениях, так что это нормально.
Ограничения
К сожалению, есть ограниченное ограничение, которое может иметь или не иметь значения для вас. Понятие "потенциально оцениваемое" гораздо более консервативно, чем пределы применения константных выражений. Так что выше noexcept
может давать ложные негативы. Он сообщит, что некоторые выражения не являются постоянными выражениями, даже если они есть. Пример:
constexpr int a = (0 ? throw "fooled!" : 42);
constexpr bool atest = isprvalconstexpr((0 ? throw "fooled!" : 42));
В выше atest
ложно, даже если инициализация a
удалось. Это потому, что для того, чтобы быть постоянным выражением, достаточно, чтобы "злые" непостоянные подвыражения "никогда не оценивались", даже если эти злые подвыражения потенциально оцениваются формально.
Да, это возможно. Один из способов сделать это (который действителен даже с недавним noexcept
изменения), чтобы воспользоваться преимуществами C++11 сужающие правила преобразования:
Сужающее преобразование - это неявное преобразование [...] из целочисленного типа или перечислимого типа с незаданной областью в целочисленный тип, который не может представлять все значения исходного типа, кроме случаев, когда источником является константное выражение, значение которого после интегральных повышений будет соответствовать в целевой тип.
(акцент мой). Инициализация списка обычно запрещает сужающие преобразования, и в сочетании с SFINAE мы можем создавать гаджеты для определения того, является ли произвольное выражение постоянным выражением:
// p() here could be anything
template<int (*p)()> std::true_type is_constexpr_impl(decltype(int{(p(), 0U)}));
template<int (*p)()> std::false_type is_constexpr_impl(...);
template<int (*p)()> using is_constexpr = decltype(is_constexpr_impl<p>(0));
constexpr int f() { return 0; }
int g() { return 0; }
static_assert(is_constexpr<f>());
static_assert(!is_constexpr<g>());
Ключевым моментом здесь является то, что int{(expr, 0U)}
содержит сужающееся преобразование из unsigned int
в int
(и, следовательно, плохо сформирован), если expr
является константным выражением, и в этом случае все выражение (expr, 0U)
константное выражение, оцененное значение которого соответствует типу int
,
C++20 добавлен std::is_constant_evaluated()
Это позволяет проверить, является ли определенное выражение постоянным вычисляемым выражением, т. Е. Оценивается во время компиляции.
Пример использования:
constexpr int foo(int num) {
// below is true in case the condition is being evaluated at compile time
// side note, using: if constexpr (std::is_constant_evaluated())
// would be evaluated always to true, so you should use a simple if!
if (std::is_constant_evaluated()) {
return foo_compiletime(num);
}
else {
return foo_runtime(num);
}
}
int main() {
constexpr auto t1 = foo(6); // reaches foo_compiletime
const auto t2 = foo(6); // reaches foo_compiletime
int n = rand() % 10;
const auto t3 = foo(n); // reaches foo_runtime
auto t4 = foo(6); // unfortunately, reaches foo_runtime
}
Последний вызов в приведенном выше примере достигнет foo_runtime , поскольку вызов не находится в контексте постоянного выражения (результат не используется как постоянное выражение, см. Также этот ответ SO ).
Это может привести к нежелательной пессимизации по сравнению со случаем, когда решение остается за пользователем, который может позвонить:
auto t4 = foo_compiletime(6);
И компилятору разрешено выполнять операции внутри foo_compiletime во время компиляции, если он объявлен как функция, или будет обязан это сделать, если он объявлен
consteval
. Однако, как только мы оставим решение компилятору, мы достигнем foo_runtime , если только мы явно не укажем компилятору перейти на foo_compiletime , передав результат в
const
,
constexpr
или же
constinit
Переменная. Что затем, в некотором смысле, опускает значение наличия одной функции для обоих сценариев, если пользователь должен помочь компилятору найти правильный путь.
Другой возможный вариант оптимизации звонка:
constexpr auto temp = foo(6); // foo_compiletime
auto t4 = temp;
Но опять же, мы требуем, чтобы пользователь знал о внутреннем поведении foo , а это не совсем то, чего мы хотим достичь.
См. Пессимизацию в этом коде.
Узнайте больше об этом в этом замечательном сообщении в блоге по данной теме .
Ниже приведена реализация is_constexpr
для функций, а не для произвольных выражений, для C++11 и C++17. Это требует, чтобы аргументы функции, которую вы хотите проверить, были конструктивными по умолчанию.
#include <type_traits>
struct A {}; // don't make it too easy, use a UDT
A f1(A a) { return a; } // is_constexpr -> false
constexpr A f2(A a) { return a; } // is_constexpr -> true
// The following turns anything (in our case a value of A) into an int.
// This is necessary because non-type template arguments must be integral
// (likely to change with C++20).
template <class T> constexpr int make_int(T &&) { return 0; }
// Helper to turn some function type (e.g. int(float)) into a function
// pointer type (e.g. int (*)(float)).
template <class T> struct signature_from;
template <class R, class... Args> struct signature_from<R(Args...)> {
using type = R(*)(Args...);
};
// See std::void_t for the idea. This does it for ints instead of types.
template <int...> using void_from_int = void;
// The fallback case: F is not a function pointer to a constexpr function
template <class T, typename signature_from<T>::type F, class = void_from_int<>>
struct is_constexpr {
static constexpr bool value = false;
};
// If void_from_int<make_int(F(Args()...))> doesn't lead to a substitution
// failure, then this is the preferred specialization. In that case F must
// be a function pointer to a constexpr function. If it is not, it could
// not be used in a template argument.
template <class R, class... Args, typename signature_from<R(Args...)>::type F>
struct is_constexpr<R(Args...), F, void_from_int<make_int(F(Args()...))>>
{
static constexpr bool value = true;
};
// proof that it works:
static_assert(!is_constexpr<A(A), f1>::value, "");
static_assert( is_constexpr<A(A), f2>::value, "");
#if __cplusplus >= 201703
// with C++17 the type of the function can be deduced:
template<auto F> struct is_constexpr2 : is_constexpr<std::remove_pointer_t<decltype(F)>, F> {};
static_assert(!is_constexpr2<f1>::value, "");
static_assert( is_constexpr2<f2>::value, "");
#endif
Смотрите его в действии на https://godbolt.org/g/rdeQme.
Давайте наивно поиграем с идиомой SFINAE:
template <typename C> struct IsConstExpr
{
typedef char yes;
typedef char no[2];
template <typename T> static constexpr yes& swallow(T) { int x[T()]; return 0; };
template <typename T> static no& swallow(...);
static const int value = sizeof(swallow<C>(0)) == sizeof(yes);
};
Код выше синтаксически неверен, но он даст нам некоторое понимание. Давайте попробуем использовать это:
constexpr int f() { return 32167; }
int g() { return 32167; }
int main()
{
std::cout << IsConstExpr<decltype(&f)>::value << std::endl;
}
Компилятор говорит:
In instantiation of 'static constexpr IsConstExpr<C>::yes& IsConstExpr<C>::swallow(T) [with T = int (*)(); C = int (*)(); IsConstExpr<C>:
:yes = char]':
Теперь проблема очевидна: параметр шаблона T = int (*)();
Это означает, что constexpr
не является частью типа, и мы не можем его обнаружить.