Equivalent ternary operator for constexpr if?

Maybe I missed something, but I can't find any hints: is there a constexpr ternary operator in C++17 equivalent to constexpr-if?

template<typename Mode>
class BusAddress {
public:
    explicit constexpr BusAddress(Address device) : 
        mAddress(Mode::write ? (device.mDevice << 1) : (device.mDevice << 1) | 0x01) {}
private:
    uint8_t mAddress = 0;    
};

Solution 1:

No, there is no constexepr conditional operator. But you could wrap the whole thing in a lambda and immediately evaluate it (an IIFE):

template<typename Mode>
class BusAddress {
public:
    explicit constexpr BusAddress(Address device)
     : mAddress([&]{
          if constexpr (Mode::write) {
            return device.mDevice << 1;
          }
          else {
            return (device.mDevice << 1) | 0x01;
          }         
        }())
     { }
private:
    uint8_t mAddress = 0;    
};

It may not be the sexiest code ever, but it gets the job done. Note that lambdas are constexpr by default where possible as of N4487 and P0170.

Solution 2:

You seem to be acting under the belief that if constexpr is a performance optimization. It isn't. If you put a constant expression in a ?: clause, any compiler worth using will figure out what it resolves to and remove the condition. So the code as you have written it will almost certainly compile down to a single option, for a particular Mode.

The principle purpose of if constexpr is to eliminate the other branch entirely. That is, the compiler doesn't even check to see if it is syntactically valid. This would be for something where you if constexpr(is_default_constructible_v<T>), and if it is true, you do T(). With a regular if statement, if T isn't default constructible, T() will still have to be syntactically valid code even if the surrounding if clause is a constant expression. if constexpr removes that requirement; the compiler will discard statements that are not in the other condition.

This becomes even more complicated for ?:, because the expression's type is based on the types of the two values. As such, both expressions need to be legal expressions, even if one of them is never evaluated. A constexpr form of ?: would presumably discard the alternative that is not taken at compile time. And therefore the expression's type should really only be based on one of them.

That a very different kind of thing.

Solution 3:

Accepted answer can also be translated into a template function for convenience:

#include <type_traits>
#include <utility>

template <bool cond_v, typename Then, typename OrElse>
decltype(auto) constexpr_if(Then&& then, OrElse&& or_else) {
    if constexpr (cond_v) {
        return std::forward<Then>(then);
    } else {
        return std::forward<OrElse>(or_else);
    }
}

// examples

struct ModeFalse { static constexpr bool write = false; };
struct ModeTrue { static constexpr bool write = true; };

struct A {};
struct B {};

template <typename Mode>
auto&& test = constexpr_if<Mode::write>(A{}, B{});

static_assert(std::is_same_v<A&&, decltype(test<ModeTrue>)>);
static_assert(std::is_same_v<B&&, decltype(test<ModeFalse>)>);

const A a;
B b;

template <typename Mode>
auto&& test2 = constexpr_if<Mode::write>(a, b);

static_assert(std::is_same_v<const A&, decltype(test2<ModeTrue>)>);
static_assert(std::is_same_v<B&, decltype(test2<ModeFalse>)>);