Можно ли создать логическое значение времени компиляции на основе того, является ли выражение C ++ 11 постоянным выражением (т.е. constexpr
) в C ++ 11? Несколько вопросов по SO относятся к этому, но я нигде не вижу прямого ответа.
По состоянию на 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. Так что вместо того, чтобы добавить его обратно, мы решили считать это дефектом по сравнению со всеми версиями стандарта и удалили его задним числом.
В результате этого, насколько мне известно, нет способа определить, можно ли использовать выражение как константное выражение.
Я однажды написал это (РЕДАКТИРОВАТЬ: см. Ниже для ограничений и объяснений). От 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
удалось. Это потому, что для того, чтобы быть постоянным выражением, достаточно, чтобы «злые» непостоянные подвыражения «никогда не оценивались», даже если эти злые подвыражения являются потенциально оцененный, формально.
Да, это возможно. Один из способов сделать это (который действителен даже с недавним 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
,
Ниже приведена реализация 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
не является частью типа а также мы не можем обнаружить это.