C++ Check If Statement Can Be Evaluated Constexpr

C++ check if statement can be evaluated constexpr

Here's another solution, which is more generic (applicable to any expression, without defining a separate template each time).

This solution leverages that (1) lambda expressions can be constexpr as of C++17 (2) the type of a captureless lambda is default constructible as of C++20.

The idea is, the overload that returns true is selected when and only when Lambda{}() can appear within a template argument, which effectively requires the lambda invocation to be a constant expression.

template<class Lambda, int=(Lambda{}(), 0)>
constexpr bool is_constexpr(Lambda) { return true; }
constexpr bool is_constexpr(...) { return false; }

template <typename base>
class derived
{
// ...

void execute()
{
if constexpr(is_constexpr([]{ base::get_data(); }))
do_stuff<base::get_data()>();
else
do_stuff(base::get_data());
}
}

Is is_constexpr possible in C++11?

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.

Behavior of If constexpr in C++

Consider this example:

#include <iostream>
#include <string>

template <typename T>
void foo() {
T t;
if constexpr (std::is_same_v<T,std::string>){
std::cout << t.find("asd");
} else {
t = 0;
std::cout << t;
}
}

int main () {
foo<int>(); // (2)
}

When T is a type that does not have a find method, then std::cout << t.find("asd") is an error. Nevertheless, the template is ok.


  1. What 'instantiaded' mean ?

The template is instantiated in (2). foo is just a template, instantiating it results in a function foo<int> that you can call.

  1. What does 'discarded' mean in the text below?

The true-branch is discarded when foo<int> is instantiated (because the condition is false). Hence, even though int has no find method, the code compiles without error.

Now consider this similar, but very different example:

#include <iostream>

int main () {
int x = 0;
if constexpr (true) {
std::cout << x;
} else {
x.find("asd");
}
}

  1. What 'checked' mean?

The text is a little contrived, it says that in the above example the false branch is discarded, but nevertheless it is checked, because it is outside of a template. It is just the english term: "checked" means the compiler checks if the code is correct. int has no method find, hence the above results in an error:

<source>:8:15: error: request for member 'find' in 'x', which is of non-class type 'int'
8 | x.find("asd");
| ^~~~

Even though this statement is never executed, it must be valid code.

Is it possible to test if a constexpr function is evaluated at compile time?

The technique listed works, but since it uses static_assert it is not sfinae friendly. A better way (in theory, you'll see what I mean) to do this is to check whether a function is noexcept. Why? Because, constant expressions are always noexcept, even if the functions are not marked as such. So, consider the following code:

template <class T>
constexpr void test_helper(T&&) {}

#define IS_CONSTEXPR(...) noexcept(test_helper(__VA_ARGS__))

test_helper is constexpr, so it will be a constant expression as long as its argument is. If it's a constant expression, it will be noexcept, but otherwise it won't be (since it isn't marked as such).

So now let's define this:

double bar(double x) { return x; }

constexpr double foo(double x, bool b) {
if (b) return x;
else return bar(x);
}

foo is only noexcept if the x is a constant expression, and b is true; if the boolean is false then we call a non constexpr function, ruining our constexpr-ness. So, let's test this:

double d = 0.0;

constexpr auto x = IS_CONSTEXPR(foo(3.0, true));
constexpr auto y = IS_CONSTEXPR(foo(3.0, false));
constexpr auto z = IS_CONSTEXPR(foo(d, true));

std::cerr << x << y << z;

It compiles, great! This gives us compile time booleans (not compile failures), which can be used for sfinae, for example.

The catch? Well, clang has a multi-year bug, and doesn't handle this correctly. gcc however, does. Live example: http://coliru.stacked-crooked.com/a/e7b037932c358149. It prints "100", as it should.

Difference between if constexpr() Vs if()

The only difference is that if constexpr is evaluated at compile time, whereas if is not. This means that branches can be rejected at compile time, and thus will never get compiled.


Imagine you have a function, length, that returns the length of a number, or the length of a type that has a .length() function. You can't do it in one function, the compiler will complain:

template<typename T>
auto length(const T& value) noexcept {
if (std::integral<T>::value) { // is number
return value;
else
return value.length();
}

int main() noexcept {
int a = 5;
std::string b = "foo";

std::cout << length(a) << ' ' << length(b) << '\n'; // doesn't compile
}

Error message:

main.cpp: In instantiation of 'auto length(const T&) [with T = int]':
main.cpp:16:26: required from here
main.cpp:9:16: error: request for member 'length' in 'val', which is of non-class type 'const int'
return val.length();
~~~~^~~~~~

That's because when the compiler instantiates length, the function will look like this:

auto length(const int& value) noexcept {
if (std::is_integral<int>::value) { // is number
return value;
else
return value.length();
}

value is an int, and as such doesn't have a length member function, and so the compiler complains. The compiler can't see that statement will never be reached for an int, but it doesn't matter, as the compiler can't guarantee that.

Now you can either specialize length, but for a lot of types (like in this case - every number and class with a length member function), this results in a lot of duplicated code. SFINAE is also a solution, but it requires multiple function definitions, which makes the code a lot longer than it needs to be compared to the below.

Using if constexpr instead of if means that the branch (std::is_integral<T>::value) will get evaluated at compile time, and if it is true then every other branch (else if and else) gets discarded. If it is false, the next branch is checked (here else), and if it is true, discard every other branch, and so on...

template<typename T>
auto length(const T& value) noexcept {
if constexpr (std::integral<T>::value) { // is number
return value;
else
return value.length();
}

Now, when the compiler will instantiate length, it will look like this:

int length(const int& value) noexcept {
//if (std::is_integral<int>::value) { this branch is taken
return value;
//else discarded
// return value.length(); discarded
}

std::size_t length(const std::string& value) noexcept {
//if (std::is_integral<int>::value) { discarded
// return value; discarded
//else this branch is taken
return value.length();
}

And so those 2 overloads are valid, and the code will compile successfully.

How to check a type has constexpr constructor

I suppose you can use SFINAE together with the power of the comma operator

Following your idea, you can rewrite your f() functions as follows

template <typename T, int = (T{}, 0)>
constexpr bool f (int)
{ return true; }

template <typename>
constexpr bool f (long)
{ return false; }

Observe the trick: int = (T{}, 0) for the second template argument

This way f() is enabled (power of the comma operator) only if T{} can be constexpr constructed (because (T{}, 0) is the argument for a template parameter), otherwise SFINAE wipe away the first version of f().

And observe that the fist version of f() receive an unused int where the second one receive a long. This way the first version is preferred, when available, calling f() with an int; the second one is selected, as better than nothing solution, when the first one is unavailable (when the first template argument isn't constexpr default constructible).

Now you can construct two template constructors for foo that you can alternatively enable/disable according the fact the template parameter T (defaulted to A) is or isn't constexpr constructible

template <typename T = A,
std::enable_if_t<f<T>(0), std::nullptr_t> = nullptr>
constexpr foo() { std::cout << "constexpr" << std::endl; }

template <typename T = A,
std::enable_if_t<not f<T>(0), std::nullptr_t> = nullptr>
constexpr foo() { std::cout << "not constexpr" << std::endl; }

The following is a full compiling example (C++14 or newer, but you can modify it for C++11):

#include <iostream>
#include <type_traits>

template <typename T, int = (T{}, 0)>
constexpr bool f (int)
{ return true; }

template <typename>
constexpr bool f (long)
{ return false; }

template <typename A>
struct foo
{
template <typename T = A,
std::enable_if_t<f<T>(0), std::nullptr_t> = nullptr>
constexpr foo() { std::cout << "constexpr" << std::endl; }

template <typename T = A,
std::enable_if_t<not f<T>(0), std::nullptr_t> = nullptr>
constexpr foo() { std::cout << "not constexpr" << std::endl; }
};

struct X1 { constexpr X1 () {} };
struct X2 { X2 () {} };

int main()
{
foo<X1> f1; // print "constexpr"
foo<X2> f2; // print "not constexpr"
}


Related Topics



Leave a reply



Submit