Reputation: 3628
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
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
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
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
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
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
Reputation: 171177
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
Reputation: 170259
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