Constexpr decltype

2019-04-30 10:11发布

问题:

I recently asked a question here (Detecting instance method constexpr with SFINAE) where I tried to do some constexpr detection at compile time. Eventually, I figured out that one can exploit noexcept to do this: any constant expression is also noexcept. So I put together the following machinery:

template <class T>
constexpr int maybe_noexcept(T && t) { return 0; }
...
constexpr bool b = noexcept(maybe_noexcept(int{}));

This works and b is true as you'd expect, as zero-initializing an int is a constant expression. It also correctly yields zero when it should (if I change int to some other appropriate type).

Next, I wanted to check if something is constexpr move constructible. So I did this:

constexpr bool b = noexcept(maybe_noexcept(int(int{})));

And again, this works properly for int, or a user defined type. However, this checks that the type has both a constexpr default constructor and a constexpr move constructor. So, to work around this, I tried to change to declval:

constexpr bool b = noexcept(maybe_noexcept(int(declval<int>())));

This results in b being false in gcc 5.3.0 (can't use clang for any of this, because clang does not correctly make constant expressions noexcept). No problem, I say, must be because declval is (interestingly enough) not marked constexpr. So I write my own naive version:

template <class T>
constexpr T&& constexpr_declval() noexcept;

Yes, this is naive compared to how the standard library does it as it will choke on void and probably other things, but it's fine for now. So I try again:

constexpr bool b = noexcept(maybe_noexcept(int(constexpr_declval<int>())));

This still does not work, b is always false. Why is this not considered a constant expression? Is this a compiler bug, or am I not understanding fundamental about constexpr? It seems like there is some strange interaction between constexpr and unevaluated contexts.

回答1:

constexpr expressions must be defined. Yours is not defined, so in that case int(constexpr_declval<int>()) is not constexpr.

Which means maybe_noexcept(int(constexpr_declval<int>())) is not a constexpr, so is not noexcept.

And the compiler properly returns false.

You also cannot invoke UB in a constexpr.

I cannot think of a way to make a constexpr reference to arbitrary data. I was thinking a constexpr buffer of aligned storage reinterpreted as a reference to the data type, but that is UB in many contexts, hence not-constexpr.

In general, this isn't possible. Imagine you had a class whose state determines if the method call is constexpr:

struct bob {
  int alice;
  constexpr bob(int a=0):alice(a) {}
  constexpr int get() const {
    if (alice > 0) throw std::string("nope");
    return alice;
  }
};

now, is bob::get constexpr or not? It is if you have a constexpr bob constructed with a non-positive alice, and ... it isn't if not.

You cannot say "pretend this value is constexpr and tell me if some expression is constexpr". Even if you could, it wouldn't solve the problem in general, because the state of a constexpr parameter can change if an expression is constexpr or not!

Even more fun, bob().get() is constexpr, while bob(1).get() is not. So your first attempt (default construct the type) even gave the wrong answer: you can test, then do the action, and the action will fail.

The object is effectively a parameter to the method, and without the state of al parameters, you cannot determine if a function is constexpr.

The way to determine if an expression is constexpr is to run it in a constexpr context and see if it works.