wimalopaan
wimalopaan

Reputation: 5462

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;    
};

Upvotes: 82

Views: 23199

Answers (4)

Madison Adventure
Madison Adventure

Reputation: 1

Just like how std::conditional could be

template <bool B, typename T, typename F>
struct conditional { using type = T; };

template <typename T, typename F>
struct conditional<false, T, F> { using type = F; };

template <bool B, typename T, typename F>
using conditional_t = typename conditional<B, T, F>::type;

you can write

template <bool B, auto T, auto F>
struct conditional_value;

template <bool B, typename val_t, val_t T, val_t F>
struct conditional_value<B, T, F> { static constexpr val_t value = T; };

template <typename val_t, val_t T, val_t F>
struct conditional_value<false, T, F> { static constexpr val_t value = F; };

template <bool B, auto T, auto F>
constexpr auto conditional_v = conditional_value<B, T, F>::value;

and then go on to use it like

template <typename T>
struct my_class
{
    static constexpr num_elems = conditional_v<sizeof(T) > 8, 16, 8>;
    std::array<int, num_elems> fields;
};

and if you really want to go wild, you can let the types diverge, but you'll probably want a convertible requirement in the struct or else use common_type

template <bool B, auto T, auto F>
struct conditional_value;

template <bool B, typename tval_t, tval_t T, typename fval_t, fval_t F>
struct conditional_value<B, T, F>
{
    // static_assert(std::is_convertible_v<tval_t,fval_t> && std::is_convertible_v<fval_t,tval_t>, "need to be able to convert");
    // using type = std::common_type_t<tval_t, fval_t>;
    // static constexpr type value = T;
    static constexpr tval_t value = T;
};

template <typename tval_t, tval_t T, typename fval_t, fval_t F>
struct conditional_value<false, T, F>
{
    // static_assert(std::is_convertible_v<tval_t,fval_t> && std::is_convertible_v<fval_t,tval_t>, "need to be able to convert");
    // using type = std::common_type_t<tval_t, fval_t>;
    // static constexpr type value = T;
    static constexpr fval_t value = F;
};

Upvotes: 0

Nicol Bolas
Nicol Bolas

Reputation: 473537

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. If used in a template where the condition is based on a template parameter, the compiler skips most of the checks for validity of the code in that branch. 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 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.

Upvotes: 42

ofo
ofo

Reputation: 1292

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>)>);

Upvotes: 0

Barry
Barry

Reputation: 303147

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.

Upvotes: 65

Related Questions