Constexpr if替代方案

26

我想使用 constexpr if 在编译时进行分支,但最新的 MSVC 编译器似乎不支持它。是否有以下内容的替代方案?

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

简而言之:当编译器不支持constexpr if时,我能模拟它吗?


5
这是C++17的一个特性。 - max66
1
我知道,问题在于最新的MSVC不完全支持C++17。 - Aart Stuurman
3
可能会感兴趣:使用C++11/C++14模拟static_if - Jarod42
7个回答

22

在C++17之前,一种方法是使用部分模板特化,例如:

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

如果您需要超过2个特殊化 - 您可以使用枚举或整数值,并为所有需要的枚举创建特殊化。

另一种方法是使用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>();
}

11

您可以使用传统的、经过验证的标签分发方式:

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

10

如果您正在使用C++14和Boost,请考虑使用Hana。 使用Hana实现,代码如下:

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

对于检测SFINAE并只在这种情况下执行某些操作的特定情况,可以简单地使用以下代码:

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

9

实际上,有几种替代方法(在if constexpr出现之前就已经在使用)。

其中一种是标签分派:

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

另一个是特征:

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

8

if constexpr 是C++17的功能;在C++17之前,从C++11开始,您可以使用std::enable_if与SFINAE。

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

-- 编辑 --

如果您只能使用C++98编译器,实现一个像std::enable_if一样工作的类型特征非常简单,可以参考以下示例:

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

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

并且这些功能变得

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

SFINAE 不需要 C++11,它在 C++ 中一直存在。 - Angew is no longer proud of SO
@Angew - 你是对的:std::enable_if 是从 C++11 开始可用的,而不是 SFINAE;谢谢;回答已修改。 - max66
如果不能使用C++11,总是可以使用boost::enable_if - Angew is no longer proud of SO
@Angew - 是的,但我更喜欢给出避免使用非标准API的答案;否则,实现类似于std :: enable_if的类型特征是如此简短和简单...感谢您的建议;答案已修改。 - max66

2

最近我遇到了困难...

因此,我创建了一个微小的if_constexpr,用于将一些c++17代码回溯到c++14。

#include <if_constexpr.hpp>

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

0

有多个答案可以解决问题(如上所述),但让我困扰的是它们要么需要太多代码(不可重用),要么依赖于某些库(例如boost.hana等)。

我提出了一个对我有效且可重用的解决方案。这是C++14(而不是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>>;

可以这样使用:

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

在这里作为一个游乐场提供:https://godbolt.org/z/G9snzWqEn


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接