Constexpr if-then-else in C++11

Viewed 410

I require constexpr if in some part of my templated codebase, but since it is not available in C++11, I decided to come up with my own version of a simpler constexpr if-then-else.

Below is my implementation of constexpr if-then-else. I am not entirely sure if it is correct, and was unable to find any relevant content anywhere which suitably explains it. It would be really helpful if someone could verify this, and/or possibly point out alternative implementations.

template <typename T, typename F>
constexpr F static_ite(std::false_type, T &&, F &&f) { return f; }

template <typename T, typename F>
constexpr T static_ite(std::true_type, T &&t, F &&) { return t; }

template <bool cond, typename T, typename F>
constexpr auto static_ite(T &&t, F &&f)
    -> decltype(static_ite(std::integral_constant<bool, cond>{}, std::forward<T>(t), std::forward<F>(f)))
{
    return static_ite(std::integral_constant<bool, cond>{}, std::forward<T>(t), std::forward<F>(f));
}

I intend to use it as a generic template. Any help would be appreciated.

3 Answers

I'm going to assume you want the function to return a reference to whatever you give it; if you instead want a copy, refer to Yakk's answer.


The return types of the first two overloads should be rvalue references, and you should std::forward when you return from them.

The long decltype could be shortened to typename std::conditional<cond, T &&, F &&>::type.

Everything else looks good to me.

template <typename T, typename F>
constexpr typename std::decay<F>::type static_ite(std::false_type, T &&, F &&f) { return std::forward<F>(f); }

and similar for other branch. References can be passed through explicitly with std ref or pointers.

I find a more generic dispatch to be also useful:

template<std::size_t N, class...Ts>
nth_type<N,typename std::decay<Ts>::type...>
dispatch_nth(index_t<N>, Ts&&...ts);

(write obvious helpers).

This lets you work on more than 2 branches.

All of these become insanely more awesome with auto lambda paramerers; while that is it was implemented in most early early implementations.

The issue with this interface is that you are evaluating both sides of the conditional, so both sides need to be valid regardless of the value of the predicate. Because of this, it is more usual to pass in lambdas one of which will be invoked within the function. In addition, you may wish to pass in extra arguments so that when an expression is type-dependent you can ensure the type is only evaluated conditionally (though to use this effectively you may need C++14 generic lambdas).

Altogether, something like this:

template <bool cond, typename T, typename F, class... Args>
constexpr auto static_ite(T &&t, F &&f, Args&&... args)
    -> typename std::result_of<typename std::conditional<cond, T, F>::type&&(Args&&...)>::type
{
    return std::forward<typename std::conditional<cond, T, F>::type>(
        std::get<cond ? 0 : 1>(std::make_tuple(std::ref(t), std::ref(f)))(
            std::forward<Args>(args)...);
}

Note that this uses the std::tuple trick to bring everything within one function body; you can use your method of std::true_type / std::false_type overloading if you prefer.

Related