Возможно ли is_constexpr в C ++ 11?

Можно ли создать логическое значение времени компиляции на основе того, является ли выражение C ++ 11 постоянным выражением (т.е. constexpr) в C ++ 11? Несколько вопросов по SO относятся к этому, но я нигде не вижу прямого ответа.

28

Решение

По состоянию на 2017 год is_constexpr невозможно в C ++ 11. Это звучит странно, поэтому позвольте мне немного рассказать об истории.

Сначала мы добавили эту функцию для устранения дефекта: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1129

Йоханнес Шауб — litb опубликовал макрос обнаружения constexpr, который опирался на положение о том, что константные выражения неявно не исключаются. Это работало в C ++ 11, но никогда не было реализовано хотя бы некоторыми компиляторами (например, clang). Затем в рамках C ++ 17 мы оценили Удаление устаревших спецификаций исключений из C ++ 17. Как побочный эффект этой формулировки, мы случайно удалили это положение. Когда основная рабочая группа обсудила вопрос о добавлении этого положения, они поняли, что с этим возникли серьезные проблемы. Вы можете увидеть полную информацию в Отчет об ошибке LLVM. Так что вместо того, чтобы добавить его обратно, мы решили считать это дефектом по сравнению со всеми версиями стандарта и удалили его задним числом.

В результате этого, насколько мне известно, нет способа определить, можно ли использовать выражение как константное выражение.

11

Другие решения

Я однажды написал это (РЕДАКТИРОВАТЬ: см. Ниже для ограничений и объяснений). От https://stackoverflow.com/a/10287598/34509 :

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 удалось. Это потому, что для того, чтобы быть постоянным выражением, достаточно, чтобы «злые» непостоянные подвыражения «никогда не оценивались», даже если эти злые подвыражения являются потенциально оцененный, формально.

28

Да, это возможно. Один из способов сделать это (который действителен даже с недавним 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,

3

Ниже приведена реализация 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.

1

Давайте сделаем наивную игру с 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 не является частью типа а также мы не можем обнаружить это.

-3
По вопросам рекламы [email protected]