Aart Stuurman
Aart Stuurman

Reputation: 3628

Constexpr if alternative

I would like to use constexpr if to branch at compile time, but it does not seem to be supported by the latest MSVC compiler. Is there an alternative to the following?:

template<typename T>
void MyFunc()
{
    if constexpr(MeetsConditions<T>::value)
    {
        FunctionA<T>();
    }
    else
    {
        FunctionB<T>();
    }
}

In short: Can I simulate constexpr if when it is not supported by the compiler?

Upvotes: 26

Views: 16119

Answers (7)

Etienne M
Etienne M

Reputation: 656

There is multiple answers that solve the problem (as proposed above) but what bother me is that they either require too much code (not reusable) or a dependency (boost.hana et al. ).

I propose a solution that works for me and is reusable. This is C++14 (not C++11 !):

        template <typename Bool>
        struct CompileTimeBranch {
        public:
            // v2

            template <typename CallIfTrue>
            static constexpr CompileTimeBranch
            True(CallIfTrue&& to_call_if_true) {
                IfTrue(Bool{}, std::forward<CallIfTrue>(to_call_if_true));
                return CompileTimeBranch{};
            }

            template <typename CallIfFalse>
            static constexpr CompileTimeBranch
            False(CallIfFalse&& to_call_if_false) {
                IfFalse(Bool{}, std::forward<CallIfFalse>(to_call_if_false));
                return CompileTimeBranch{};
            }

            // v1

            template <typename CallIfTrue, typename CallIfFalse>
            static constexpr void
            Then(CallIfTrue&&  to_call_if_true,
                 CallIfFalse&& to_call_if_false) {

                // v2 Re-use True/False
                True(std::forward<CallIfTrue>(to_call_if_true));
                False(std::forward<CallIfFalse>(to_call_if_false));

                // v1 Less verbose but less versatile
                // Branch(Bool{},
                //        std::forward<CallIfTrue>(to_call_if_true),
                //        std::forward<CallIfFalse>(to_call_if_false));
            }

            constexpr operator bool() const {
                return Bool::value;
            }

        protected:
            // v2

            template <typename CallIfTrue>
            static constexpr void
            IfTrue(std::true_type,
                   CallIfTrue&& to_call_if_true) {
                to_call_if_true(Bool{});
            }

            template <typename CallIfTrue>
            static constexpr void
            IfTrue(std::false_type,
                   CallIfTrue&&) {}

            template <typename CallIfFalse>
            static constexpr void
            IfFalse(std::true_type,
                    CallIfFalse&&) {}

            template <typename CallIfFalse>
            static constexpr void
            IfFalse(std::false_type,
                    CallIfFalse&& to_call_if_false) {
                to_call_if_false(Bool{});
            }

            // v1

            // template <typename CallIfTrue, typename CallIfFalse>
            // static constexpr void
            // Branch(std::true_type, CallIfTrue&& to_call_if_true, CallIfFalse&&) {
            //     to_call_if_true(Bool{});
            // }

            // template <typename CallIfTrue, typename CallIfFalse>
            // static constexpr void
            // Branch(std::false_type, CallIfTrue&&, CallIfFalse&& to_call_if_false) {
            //     to_call_if_false(Bool{});
            // }
        };

        template <bool kBranchLiteral>
        using LiteralCompileTimeBranch = CompileTimeBranch<std::integral_constant<bool, kBranchLiteral>>;

Usable like so:

template <typename T> void AssertIfSmall() {
    static_assert(sizeof(T) <= 4, "");
}

template <typename T> void test0() {
    if (sizeof(T) <= 4) {
        AssertIfSmall<T>(); // Wont compile
        std::printf("Small stuff\n");
    } else {
        std::printf("Big stuff\n");
    }
}

template <typename T> void test1() {
    if constexpr (sizeof(T) <= 4) { // Expected C++17 behavior
        AssertIfSmall<T>();
        std::printf("Small stuff\n");
    } else {
        std::printf("Big stuff\n");
    }
}

template <typename T> void test2() {
    using Branch0 = LiteralCompileTimeBranch<sizeof(T) >= 1>;
    using Branch = LiteralCompileTimeBranch<sizeof(T) <= 4 && Branch0{}>;

    Branch::Then(
        [](auto) {
            AssertIfSmall<T>();
            std::printf("Small stuff\n");
        },
        [](auto) { std::printf("Big stuff\n"); });
}

template <typename T> void test3() {
    using Branch = CompileTimeBranch<std::integral_constant<bool, sizeof(T) <= 4>>;
    Branch::True([](auto) { AssertIfSmall<T>();
                            std::printf("Small stuff\n"); });
    Branch::False([](auto) { std::printf("Big stuff\n"); });
}

template <typename T> void test4() {
    using Branch = CompileTimeBranch<std::integral_constant<bool, sizeof(T) <= 4>>;
    Branch::True([](auto) {
        AssertIfSmall<T>();
        std::printf("Small stuff\n");
    }).False([](auto) {
        std::printf("Big stuff\n");
    });
}

Available here as a playground: https://godbolt.org/z/G9snzWqEn

Upvotes: 0

Starl1ght
Starl1ght

Reputation: 4493

One of pre-C++17 ways is to use partial template specializations, like here:

template <typename T, bool AorB>
struct dummy;

template <typename T, true>
struct dummy {
    static void MyFunc() {  FunctionA<T>(); }
}

template <typename T, false>
struct dummy {
    static void MyFunc() {  FunctionB<T>(); }
}

template <typename T>
void Facade() {
    dummy<T, MeetsConditions<T>::value>::MyFunc();
}

If you need more, than 2 specializations - you can use enum or integral value, and make specializations for all needed enums.

Another way is to use std::enable_if:

template <typename T>
std::enable_if<MeetsConditions<T>::value, void>::type
MyFunc() {
   FunctionA<T>();
}

template <typename T>
std::enable_if<!MeetsConditions<T>::value, void>::type
MyFunc() {
   FunctionB<T>();
}

Upvotes: 22

Garcia Sylvain
Garcia Sylvain

Reputation: 396

I recently get stuck on that... So, I created a tiny if_constexpr library for back-porting some c++17 code to c++14.

#include <if_constexpr.hpp>

template<typename T>
constexpr void MyFunc()
{
    using namespace ic;
    if_<MeetsConditions<T>::value>([] {
        FunctionA<T>();
    }, else_([] {
        FunctionB<T>();
    }));
}

Upvotes: 3

Justin
Justin

Reputation: 25387

If you are using C++ 14 and Boost, consider using Hana. Implemented using Hana, this looks something like this:

template<typename T>
void MyFunc()
{
    hana::eval_if(MeetsConditions<T>::value,
        [](auto) { FunctionA<T>(); },
        [](auto _) { FunctionB<T>(_(exprThatWouldOtherwiseBeAnError)); }
    );
}

For the specific case of detecting SFINAE and executing something only in that case, that could be as simple as:

template<typename T>
void MyFunc()
{
    auto maybeDoFunctionA = hana::sfinae([]() -> decltype((void) FunctionA<T>()) {
        FunctionA<T>();
    });
}

Upvotes: 10

max66
max66

Reputation: 66230

if constexpr is a C++17 feature; before C++17, starting from C++11, you can use SFINAE with std::enable_if

template<typename T>
typename std::enable_if<true == MeetsConditions<T>::value>::type MyFunc ()
{ FunctionA<T>(); }

template<typename T>
typename std::enable_if<false == MeetsConditions<T>::value>::type MyFunc ()
{ FunctionB<T>(); }

-- EDIT --

If you can use only a C++98 compiler, implement a type traits that work like std::enable_if is really simple; see the following example

template <bool, typename = void>
struct enableIf
 { };

template <typename T>
struct enableIf<true, T>
 { typedef T type; };

and the functions become

template<typename T>
typename enableIf<true == MeetsConditions<T>::value>::type MyFunc ()
{ FunctionA<T>(); }

template<typename T>
typename enableIf<false == MeetsConditions<T>::value>::type MyFunc ()
{ FunctionB<T>(); }

Upvotes: 8

There are several alternatives indeed (which have been in use long before if constexpr started to exist).

One is tag dispatch:

template <class T>
void Function(std::true_type)
{
  FunctionA<T>();
}

template <class T>
void Function(std::false_type)
{
  FunctionB<T>();
}

template <class T>
void MyFunc()
{
  Function<T>(std::integral_constant<bool, MeetsCondition<T>::value>{});
}

Another one are traits:

template <bool B>
struct FunctionTraits;

template <>
struct FunctionTraits<true>
{
  template <class T>
  static void Call() { FunctionA<T>(); }
};

template <>
struct FunctionTraits<false>
{
  template <class T>
  static void Call() { FunctionB<T>(); }
};

template <class T>
void MyFunc()
{
  FunctionTraits<MeetsCondition<T>::value>::Call<T>();
}

Upvotes: 9

You can do it the old fashioned, tried and tested tag dispatch way:

template<typename T>
void MyFuncImpl(std::true_type) {
  FunctionA<T>();
}

template<typename T>
void MyFuncImpl(std::false_type) {
  FunctionB<T>();
}

template<typename T>
void MyFunc()
{
  MyFuncImpl<T>(std::integral_constant<bool, MeetsConditions<T>::value>{});
}

Upvotes: 11

Related Questions