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

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

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

4b9b3361

Ответ 1

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

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

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

Эффект от этого заключается в том, что, насколько мне известно, нет способа определить, можно ли использовать выражение как константное выражение.

Ответ 2

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

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 и throwable dynamic_cast или typeid также не допускаются в константных выражениях, так что это нормально.

Ограничения

К сожалению, есть тонкое ограничение, которое может иметь или не иметь значения для вас. Понятие "потенциально оцениваемое" гораздо более консервативно, чем пределы применения константных выражений. Таким образом, вышеприведенное noexcept может давать ложные отрицания. Он сообщит, что некоторые выражения не являются постоянными выражениями prvalue, даже если они есть. Пример:

constexpr int a = (0 ? throw "fooled!" : 42);
constexpr bool atest = isprvalconstexpr((0 ? throw "fooled!" : 42));

В приведенном выше atest ложно, хотя инициализация a успешно. Это потому, что для того, чтобы быть постоянным выражением, достаточно, чтобы "злые" непостоянные подвыражения "никогда не оценивались", даже если эти злые подвыражения потенциально оцениваются формально.

Ответ 3

Да, это возможно Один из способов сделать это (который действителен даже при недавних изменениях, за noexcept изменений) - воспользоваться преимуществами сужающих правил преобразования С++ 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.

Ответ 4

Ниже приведена реализация is_constexpr для функций, а не для произвольных выражений, для С++ 11 и С++ 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.

Ответ 5

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