C++检查if语句是否能够被constexpr计算

39

是否有一种方法可以决定某个东西是否可以进行constexpr评估,并将结果用作constexpr布尔值? 我的简化用例如下:

有没有办法判断一个东西是否能够被 constexpr 评估, 并且把结果用作 constexpr 布尔值呢?我的简单应用场景如下:


template <typename base>
class derived
{
    template<size_t size>
    void do_stuff() { (...) }

    void do_stuff(size_t size) { (...) }
public:
    void execute()
    {
        if constexpr(is_constexpr(base::get_data())
        {
            do_stuff<base::get_data()>();
        }
        else
        {
            do_stuff(base::get_data());
        }
    }
}

我的目标是学习C++2a。

我发现了以下Reddit串,但我不太喜欢宏定义。https://www.reddit.com/r/cpp/comments/7c208c/is_constexpr_a_macro_that_check_if_an_expression/


1
嗯,if constexpr 的主体只有在编译时 if constexpr 中的表达式为真时才会被评估。这是您要找的吗? - Jesper Juhl
1
但是如果 if constexpr([test]) 中的测试在编译时无法评估怎么办? - Aart Stuurman
10
你可以尝试使用 std::is_constant_evaluated,这个函数可以判断当前代码是否在常量表达式中被评估。 - 0x5453
https://en.cppreference.com/w/cpp/language/if - Jesper Juhl
3
“do_stuff”是什么,它既可以在编译时运行也可以在运行时运行,但本身不应该是“constexpr”吗?把它作为“constexpr”函数是否更合理,然后将“get_data”的值作为参数传递给它? - Nicol Bolas
显示剩余3条评论
3个回答

45

这里有另一种更通用的解决方案(适用于任何表达式,无需每次都定义一个独立的模板)。

该解决方案充分利用了以下两点:(1)自 C++17 以来,Lambda 表达式可以是 constexpr;(2)自 C++20 以来,没有捕获的 Lambda 的类型是可默认构造的。

其思路是:仅在 Lambda{}() 可出现在模板参数中时选择返回 true 的重载,这实际上要求 Lambda 调用是一个常量表达式。

template<class Lambda, int=(Lambda{}(), 0)>
constexpr bool is_constexpr(Lambda) { return true; }
constexpr bool is_constexpr(...) { return false; }

template <typename base>
class derived
{
    // ...

    void execute()
    {
        if constexpr(is_constexpr([]{ base::get_data(); }))
            do_stuff<base::get_data()>();
        else
            do_stuff(base::get_data());
    }
}

有趣的解决方案...这样你就可以得到与我的自定义类型特征相同的结果,但更加合成,并且最重要的是,确切的表达式(base::get_data())被嵌入到参数中,而不像我的解决方案那样硬编码。非常好。我得记住它。 - max66
我接受这个答案,因为它回答了问题的一般情况。max66的答案在非c++2a的情况下也非常有用,但需要每次使用都进行重复 :) - Aart Stuurman
4
逗号运算符SFINAE...我的脑袋炸了。 - organicoman
一个启发性的解决方案。我已经对此进行了一些测试,并且我相信使用逗号SFINAE是不必要的,我很确定形式为template<typename T,auto = T()()>的模板,其中我的T是你的Lambda同样适用。 - user2628206
再想一下,由于lambda表达式的返回类型为void,因此需要逗号SFINE。 (在我的测试中,我使用了类似于[]{ base::get_data(); return true;}的lambda表达式,它始终是非void的。 - user2628206

16

这并不完全符合您的要求(我为特定的get_value()静态方法开发了自定义类型特征……也许可以通用化,但目前我不知道如何),但我想您可以使用SFINAE并按以下方式制作一些内容。

#include <iostream>
#include <type_traits>

template <typename T>
constexpr auto icee_helper (int)
   -> decltype( std::integral_constant<decltype(T::get_data()), T::get_data()>{},
                std::true_type{} );

template <typename>
constexpr auto icee_helper (long)
   -> std::false_type;

template <typename T>
using isConstExprEval = decltype(icee_helper<T>(0));

template <typename base>
struct derived
 {
   template <std::size_t I>
   void do_stuff()
    { std::cout << "constexpr case (" << I << ')' << std::endl; }

   void do_stuff (std::size_t i)
    { std::cout << "not constexpr case (" << i << ')' << std::endl; }

   void execute ()
    {
      if constexpr ( isConstExprEval<base>::value )
         do_stuff<base::get_data()>();
      else
         do_stuff(base::get_data());
    }
 };

struct foo
 { static constexpr std::size_t get_data () { return 1u; } };

struct bar
 { static std::size_t get_data () { return 2u; } };

int main ()
 { 
   derived<foo>{}.execute(); // print "constexpr case (1)"
   derived<bar>{}.execute(); // print "not constexpr case (2)"
 }

7
这种使用逗号运算符和长整型/整型重载的方式太疯狂了……不过还是点个赞吧。:/ - matovitch
2
@matovitch - 永远不要低估逗号运算符的威力 }:‑) - max66
这个代码能在 sizeof(long) 等于 sizeof(int) 的平台上运行吗? - Greg Nisbet
3
是的,因为对于语言和编译器而言,它们仍然是不同的类型。 - max66

11
template<auto> struct require_constant;
template<class T>
concept has_constexpr_data = requires { typename require_constant<T::get_data()>; };

这基本上是由 std::ranges::split_view 所使用的。


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