Constexpr Additions in C++17

Contents

Last year, I have written about constexpr and compile time constants in general. Last week, the Draft International Standard for C++17 has been sent to its ballot. Time to provide some updates to earlier posts!

While the new standard won’t be official until much later this year, the big compilers already are implementing many C++17 features. Many of them are already available on released versions or on trunk builds of those compilers. If you want to try the new constexpr additions without having to build your own compiler from source, have a look at pages like Compiler Explorer or Wandbox.

Constexpr lambdas

Lambdas have become constexpr ready in C++17. That means they can be used in constexpr contexts and the closure objects are of literal types, as long as the captured members have literal types as well.

template <typename I>
constexpr auto adder(I i) {
  //use a lambda in constexpr context
  return [i](auto j){ return i + j; }; 
}

//constexpr closure object 
constexpr auto add5 = adder(5);

template <unsigned N>
class X{};

int foo() {
  //use in a constant expression
  X<add5(22)> x27;

  //...
}

I know this will make it still harder for some to not suffer from “Overlambdification”, but what can we do – it’s already out there (call the police!)

Constexpr if

With C++17 we have the possibility to evaluate conditional expressions at compile time. The compiler is then able to eliminate the false branch completely. From a certain point of view, compilers did that already if you had an if-statement with a condition that was a compile time constant: Compilers and optimizers were able to figure out when our code contained a glorified if (true) and would optimize away the else branch.

However with the old if, the other branch still had to compile. With if constexpr that is no longer the case, the compiler will no longer attempt that. Of course it still needs to be valid C++ syntax, since the parser at least needs to figure out where the conditional block ends.

template <class T>
auto foo(T t) {
  if constexpr(std::is_same_v<T, X>) {
    return t.a_function_that_exists_only_for_X();
  } else {
    std::cout << t << '\n';
    return;
  }
}

void bar() {
  X x;
  auto i = foo(x);
  foo(23);
}

Here, the a_function_that_exists_only_for_X method can not be called for anything but an object of type X. The call foo(23) would lead to a compile error had the condition been a plain old if. In addition, you see that depending on the branch the compiler takes, the return type of the function differs. It is int for parameters of type X and void for anything else.

In practice, this is very much the same as if the compiler had taken the two branches apart for us into two separate functions:

auto foo(X x) {
  return x.a_function_that_exists_only_for_X();
}

template <class T>
auto foo(T t) {
  std::cout << t << '\n';
}

In fact, that’s how we should have written these functions in the first place. They do unrelated things (unless the X method is a weird print functionality) and return different things. They are two functions that are unrelated except for their very generic name. Don’t stitch together what does not belong together.

On the other hand, if constexpr allows us to put together what does belong together but had to be kept apart until now. Many use cases of tag dispatch, enable_if and special case overloads can be solved with if constexpr.

I had an example in a post about variadic templates:

template <class Head, class... Tail>
void print(Head const& head, Tail const&... tail){
  std::cout << head;
  if constexpr(sizeof...(tail) > 0) {
    std::cout << ", ";
    print(tail...);
  }
}

Before C++17, those functions would have to be divided into the generic one and a special case function that takes only the Head parameter.

Constexpr fixes to the standard library

There were some features in the standard library that lacked constexpr specifiers. These have been added in many places. Most notable are std::array and range access functions like std::begin and std::end etc.

That means, std::array is now a literal type as long as the element type is a literal type, and most of its operations can be used at compile time. Of course, the constexpr-ness of std::begin and the like depends on the container: Since std::vector is not a literal type, neither vec.begin() nor std::begin(vec) are constexpr, but std::begin(arr) is constexpr for both C-arrays and std::array.

Previous Post
Next Post

2 Comments


  1. We are using constexpr to specify static data members with complex types using
    static constexpr auto blah = f_returns_complex_type();

    The initializer must be constexpr too, and you have to drop from std:string to const char*, but for our use it works.

    NB: gcc6.2 has a bug where the linker fails for example on the following:
    struct CompilesButNotLinksUnderGcc62 {
    static constexpr auto tp = make_tuple(“a”, 1);
    }

    This seems to be fixed in gcc 6.3.1

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *