Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constexpr decltype

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.

like image 873
Nir Friedman Avatar asked Feb 08 '16 18:02

Nir Friedman


People also ask

What does decltype do in C++?

The decltype type specifier yields the type of a specified expression. The decltype type specifier, together with the auto keyword, is useful primarily to developers who write template libraries. Use auto and decltype to declare a function template whose return type depends on the types of its template arguments.

What is the use of constexpr?

constexpr indicates that the value, or return value, is constant and, where possible, is computed at compile time. A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations.

What does constexpr mean in C++?

constexpr stands for constant expression and is used to specify that a variable or function can be used in a constant expression, an expression that can be evaluated at compile time. The key point of constexpr is that it can be executed at compile time.

Is constexpr same as #define?

#define directives create macro substitution, while constexpr variables are special type of variables. They literally have nothing in common beside the fact that before constexpr (or even const ) variables were available, macros were sometimes used when currently constexpr variable can be used.


1 Answers

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.

like image 170
Yakk - Adam Nevraumont Avatar answered Oct 07 '22 08:10

Yakk - Adam Nevraumont