Constexpr if alternative
Asked Answered
S

7

26

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?

Scowl answered 24/4, 2017 at 11:55 Comment(3)
It's a c++17 featureRibald
Yes I know, the problem is the newest MSVC does not fully support C++17.Scowl
Might be interesting: simulate-static_if-with-c11c14Chickabiddy
B
22

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>();
}
Bland answered 24/4, 2017 at 12:1 Comment(0)
A
11

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>{});
}
Alguire answered 24/4, 2017 at 12:0 Comment(0)
J
10

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>();
    });
}
Juliajulian answered 24/4, 2017 at 17:1 Comment(0)
F
9

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>();
}
Factional answered 24/4, 2017 at 12:1 Comment(0)
R
8

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>(); }
Ribald answered 24/4, 2017 at 12:0 Comment(4)
SFINAE doesn't require C++11, it was always there in C++.Factional
@Angew - your right: is std::enable_if that is available from C++11, not SFINAE; thanks; answer modifiedRibald
There's always boost::enable_if if C++11 is not an option.Factional
@Angew - yes, but I prefer give answer avoiding not-standard api; otherwise, implement a std::enable_if like type traits it's so short and simple ... thanks for the suggestion; answer modifiedRibald
L
2

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>();
    }));
}
Luzern answered 14/9, 2020 at 18:5 Comment(0)
D
0

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

Daveta answered 1/11, 2022 at 20:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.