Is is_constexpr possible in C++11?

2019-01-03 16:25发布

Is it possible to produce a compile-time boolean value based on whether or not a C++11 expression is a constant expression (i.e. constexpr) in C++11? A few questions on SO relate to this, but I don't see a straight answer anywhere.

5条回答
再贱就再见
2楼-- · 2019-01-03 17:05

I once wrote it (EDIT: see below for limitations and explanations). From 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))

However there are many kinds of constant expressions. The above answer detects prvalue constant expressions.


Explanation

The noexcept(e) expression gives false iff e contains

  • a potentially evaluated call to a function that does not have a non-throwing exception-specification unless the call is a constant expression,
  • a potentially evaluated throw expression,
  • a potentially evaluated throwable form of dynamic_cast or typeid.

Note that the function template makeprval is not declared noexcept, so the call needs to be a constant expression for the first bullet not to apply, and this is what we abuse. We need the other bullets to not apply aswell, but thanksfully, both a throw and a throwable dynamic_cast or typeid aren't allowed in constant expressions aswell, so this is fine.

Limitations

Unfortunately there is a suble limitation, which may or may not matter for you. The notion of "potentially evaluated" is much more conservative than the limits of what constant expressions apply. So the above noexcept may give false negatives. It will report that some expressions aren't prvalue constant expressions, even though they are. Example:

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

In the above atest is false, even though the initialization of a succeeded. That is because for being a constant expression, it suffices that the "evil" non-constant sub-expressions are "never evaluated", even though those evil sub-expressions are potentially-evaluated, formally.

查看更多
男人必须洒脱
3楼-- · 2019-01-03 17:09

The following is an implementation of is_constexpr for functions, not for arbitrary expressions, for C++11 and C++17. It requires the arguments to the function you want to test to be default constructible, though.

#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

See it in action at https://godbolt.org/g/rdeQme.

查看更多
可以哭但决不认输i
4楼-- · 2019-01-03 17:12

As of 2017, is_constexpr is not possible in C++11. That sounds like an odd thing to say, so let me explain a bit of the history.

First, we added this feature to resolve a defect: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1129

Johannes Schaub - litb posted a constexpr detection macro that relied on the provision that constant expressions are implicitly noexcept. This worked in C++11, but was never implemented by at least some compilers (for instance, clang). Then, as part of C++17, we evaluated Removing Deprecated Exception Specifications from C++17. As a side-effect of that wording, we accidentally removed that provision. When the Core Working Group discussed adding the provision back in, they realized that there were some serious problems with doing so. You can see the full details in the LLVM bug report. So rather than adding it back in, we decided to consider it a defect against all versions of standard and retroactively removed it.

The effect of this is that there is, to my knowledge, no way to detect whether an expression is usable as a constant expression.

查看更多
做自己的国王
5楼-- · 2019-01-03 17:12

Yes, this is possible. One way to do it (which is valid even with the recent noexcept changes) is to take advantage of the C++11 narrowing conversion rules:

A narrowing conversion is an implicit conversion [...] from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression whose value after integral promotions will fit into the target type.

(emphasis mine). List initialization generally disallows narrowing conversions, and when combined with SFINAE we can build gadgets for detecting whether an arbitrary expression is a constant expression:

// 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>());

Live demonstration.

The key here is that int{(expr, 0U)} contains a narrowing conversion from unsigned int to int (and thus is ill-formed), unless expr is a constant expression, in which case the entire expression (expr, 0U) is a constant expression whose evaluated value fits into the type int.

查看更多
家丑人穷心不美
6楼-- · 2019-01-03 17:18

Let's do some naive play with the SFINAE idiom:

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);
};

The code above is syntactically wrong, but it will give us some insight. Let's try to make use of it:

constexpr int f() { return 32167; }

int g() { return 32167; }

int main()
{
   std::cout << IsConstExpr<decltype(&f)>::value << std::endl;
}

The compiler says:

In instantiation of 'static constexpr IsConstExpr<C>::yes& IsConstExpr<C>::swallow(T) [with T = int (*)(); C = int (*)(); IsConstExpr<C>:
:yes = char]':

Now the problem is obvious: the parameter of the template is T = int (*)();

It means that constexpr is not part of type and we cannot detect it.

查看更多
登录 后发表回答