C++17: let’s have a look at the constexpr if

There was a long discussion in the C++ community about when and how to implement the static if, the original proposal from Bright, Sutter and Alexandrescu which was aiming to define a possible implementation of this feature was resurrected after a quite long silence, just to be included in the upcoming C++17.

The actual implementation of the static if (aka, constexpr if) is much more versatile and useful than the original idea coming in the first description from Sutter and friends,. The actually accepted proposal from Ville Voutilainen may not be the final one, before the release of C++17 we may eventually see some slightly changes in the implementation of the constexpr if.

Here I’ll write about the Voutilainen constexpr if with the modification introduced by Jens Maurer in this paper. This is the way the static if is actually implemented in LLVM, please make sure to checkout always the latest revision from the official repository if you want to try yourself how this feature works, in this post I’ll be testing the code with clang version 3.9.0 (trunk 275047).

So let’s pick the definition of the constexpr selection statement as from the current standard draft:

If the parenthesized condition is prefixed with constexpr, the condition shall be a contextually converted constant expression of type bool (5.20 [expr.const]); this form is called a constexpr if statement. If the value of the converted condition is false, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement. A case or default label appearing within such an if statement shall be associated with a switch statement (6.4.2 [stmt.switch]) within the same if statement. A label (6.1 [stmt.label]) declared in a discarded statement shall not be referred to by a statement (6.6.4 [stmt.goto]) outside of a discarded statement. When a constexpr if statement appears in a templated entity, during an instantiation of the enclosing template or generic lambda, a discarded statement is not instantiated. [ Example:

— end example] [ Note: Odr-uses (3.2 [basic.def.odr]) in a discarded statement do not require an entity to be defined. — end note ]

I’ll start with some old school piece of code to show what actually is achievable with the new statement and how much it simplifies the implementation and most importantly the understanding of certain functionalities.

The “Problem”.

Say we want to selectively disable a class member function for a given type, actually we have at least tree possible ways of achieving this: Tree are kind of mainstream and the fourth one is a less practical and substantially usable in limited situations.

I will concentrate my post on the three more frequently used techniques. From the code below, to disable the function bar which is a member of foo, we’ll use the SFINAE programming technique with std::enable_if, Let’s see the first of the three implementations:

template<typename T>
class foo
{
public:
    template<typename D = T, typename std::enable_if<
                 std::is_same<D,int>::value,int>::type...>
    void bar(int arg)
    {
        std::cout<<"Calling first bar: "<<arg<<std::endl;
    }

    template<typename D = T, typename std::enable_if<
                 !std::is_same<D,int>::value,int>::type...>
    void bar(int arg)
    {
        std::cout<<"Calling second bar: "<<arg<<std::endl;
    }
};

This template class has an overloaded (kind of) function bar, the difference in the two versions of the function is all in the template parameters, one of them is the same for both D = T and the second one is not equal and it’s type depends on the type resulting from the expression ‘std::enable_if<std::is_same<D,int>::value,int>::type‘.

is_same will have the field value equal to true if D is an int and this will trigger enable_if to provide a define for type which would be equal to D, otherwise there will be no typedef, for details about enable_if have a look here.

Note that the second bar function has exactly the same condition but with an additional negation inside the enable_if,  in this way the two functions are not identical and the resolution will work properly when bar is invoked, the first version of the function will be used when D is and int (and since D == T then T is an int), in all the other cases the second implementation will be chosen by the compiler.

Let’s see how we can test this code:

    foo<bool> f;
    f.bar(69);
    foo<int> f2;
    f2.bar(70);

The first instantiation of foo with the template argument bool will cause the invocation of f.bar(69) to be resolved with the second implementation of bar, whereas the second call of bar for f2 will cause the first implementation to be invoked.

So that’s nice, we can selectively enable and disable a given function on the base of the template argument provided to foo, for some more details about what’s going on have a look here.

We can achieve the very same thing in a little different way:

template<typename T>
class foo
{
public:
    template<typename D = T>
    typename std::enable_if<std::is_same<D,int>::value>::type bar(int arg)
    {
        std::cout<<"Calling first bar: "<<arg<<std::endl;
    }

    template<typename D = T>
    typename std::enable_if<!std::is_same<D,int>::value>::type bar(int arg)
    {
        std::cout<<"Calling second bar: "<<arg<<std::endl;
    }
};

The difference in the two version of bar is all in the return type, I’m using the same expression explained earlier but here the typedef type of enable_if is used to provide a return type for bar.

We have two situations, if T is an int, then is_same is true and the first implementation of bar is well formed, and the second one is ill formed, an attempt to compile it will cause and error and is not considered during the call resolution. The vice-versa is true when T is not an int. This mean, that depending on the type of the compiler will pick one of the two versions of bar.

The third way to again achieve the same goal substantially consist of using again enable_if but this time as function argument:

template<typename T>
class foo
{
public:
    template<typename D = T>
    void bar(int arg, typename std::enable_if<std::is_same<D,int>::value>::type* = nullptr)
    {
        std::cout<<"Calling first bar: "<<arg<<std::endl;
    }

    template<typename D = T>
    void bar(int arg, typename std::enable_if<!std::is_same<D,int>::value>::type* = nullptr)
    {
        std::cout<<"Calling second bar: "<<arg<<std::endl;
    }
};

Nothing really new happen here, the mechanism is the same but applied in a different place.

In all three cases we have obtained to selectively enable or disable a certain function, at compile time on the base of the value of T the compiler will pick one of the two bar and will ignore the other. This mechanism if frequently used in the C++ standard library, you may want to have a look yourself at the header from your STL implementation.

What about constexpr if?

Well, let’s see how is possible to achieve the same identical thing with this brand new feature:

template<typename T>
class foo
{
public:
    void bar(int arg)
    {
        if constexpr(std::is_same<T,int>::value)
        {
            std::cout<<"Calling first bar: "<<arg<<std::endl;
        }
        else
        {
            std::cout<<"Calling second bar: "<<arg<<std::endl;
        }
    }
};

Isn’t this code much more readable?

There is only one bar function, it’s body has two parts: The first one is considered by the compiler only if the condition in the expression is true, if it’s false then the body of this bar function will be equal to what’s inside the else statement.

The condition in the constexpr if must be a constant expression, this mean that the compiler must be able to evaluate the condition at compile time. The branch of the statement which is not considered will be parsed anyway, this means that error (for example syntax error) can be prompted if any is present in that code, even if the final function body will have no trace of those lines.

The fact that is possible to enable or disable a given portion of code at compile time, allows the developer to implement in a much more readable way functions that normally would required some non necessarily self explanatory template code, for example the classical Fibonacci function:

using ull = unsigned long long;

template<ull n>
constexpr ull fibonacci()
{
    if constexpr(n>=2)
    {
        return fibonacci<n-1>() + fibonacci<n-2>();
    }
    else
    {
        return n;
    }
}

Here the compiler will choose which branch to use on the base of the compile time value of n.

So we have a pretty interesting and useful feature, clearly here I’ve just show two simple use case for this feature, using the constexpr if statement is possible to simplify a lot of code which normally would be potentially hard to understand because of the nasty use of template magic.

Thanks for reading

Leave a Reply