constexpr函数中的编译时或运行时检测 [英] Compile-time or runtime detection within a constexpr function

查看:56
本文介绍了constexpr函数中的编译时或运行时检测的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当在C ++ 11中引入constexpr时,我感到很兴奋,但是不幸的是,我对它的有用性做出了乐观的假设。我假设我们可以在任何地方使用constexpr来捕获文字编译时常量或文字编译时常量的任何constexpr结果,包括以下内容:

I was excited when constexpr was introduced in C++11, but I unfortunately made optimistic assumptions about its usefulness. I assumed that we could use constexpr anywhere to catch literal compile-time constants or any constexpr result of a literal compile-time constant, including something like this:

constexpr float MyMin(constexpr float a, constexpr float b) { return a<b?a:b; }

因为仅对函数的返回类型进行限定,因为constexpr不会将其用法限制为编译时,并且必须在运行时也必须可调用,我认为这是确保MyMin只能与编译时评估的常量一起使用的一种方式,并且这将确保编译器永远不会在运行时允许其执行,从而解放了我MyMin的另一种对运行时更友好的替代版本,理想情况下,其名称与使用_mm_min_ss内在函数的名称相同,以确保编译器不会生成运行时分支代码。不幸的是,函数参数不能是constexpr,因此似乎无法完成,除非可能这样:

Because qualifying a function's return type only as constexpr does not limit its usage to compile-time, and must also be callable at runtime, I figured that this would be a way to ensure that MyMin can only ever be used with compile-time evaluated constants, and this would ensure that the compiler would never allow its execution at runtime, freeing me to write an alternative more runtime friendly version of MyMin, ideally with the same name that uses a _mm_min_ss intrinsic, ensuring that the compiler won't generate runtime branching code. Unfortunately, function parameters cannot be constexpr, so it would seem that this cannot be done, unless something like this were possible:

constexpr float MyMin(float a, float b)
{
#if __IS_COMPILE_TIME__
    return a<b?a:b;
#else
    return _mm_cvtss_f32(_mm_min_ss(_mm_set_ss(a),_mm_set_ss(b)));
#endif
}

我非常怀疑MSVC ++是否具有这样的功能根本没有,但是我希望GCC或clang至少可以完成一些任务,但是看起来可能很简单。

I have serious doubts that MSVC++ has anything like this at all, but I was hoping maybe GCC or clang have at least something to accomplish it, however unelegant it may look like.

当然,我展示的示例非常简单,但是如果您能发挥自己的想象力,那么在许多情况下,您可以随意做一些事情,例如在您知道只能在编译时执行的函数中大量使用分支语句,因为如果在运行时执行,性能会

Granted, the example I presented was very simplistic, but if you can use your imagination, there are many cases where you could feel free to do something like make extensive use of branching statements within a function that you know can only execute at compile time, because if it executed at runtime, performance would be compromised.

推荐答案

可以检测给定的函数调用表达式是否为常量表达式,从而在两个表达式之间进行选择不同的实现。

It is possible to detect if a given function-call expression is a constant expression, and thereby select between two different implementations. Requires C++14 for the generic lambda used below.

(此答案在这个来自@Yakk的答案是我去年提出的一个问题)。

(This answer grew out this answer from @Yakk to a question I asked last year).

我不确定我将标准推进多远。这在clang 3.9上进行了测试,但是导致g ++ 6.2给出内部编译器错误。我将在下周发送错误报告(如果没有其他人先这样做!)

I'm not sure how far I'm pushing the Standard. This is tested on clang 3.9, but causes g++ 6.2 to give an "internal compiler error". I'll send a bug report next week (if nobody else does it first!)

第一步是移动 constexpr 实现转换为结构作为 constexpr静态方法。更简单地说,您可以保留当前的 constexpr 并从新的<$的 constexpr静态方法调用它c $ c> struct

This first step is to move the constexpr implementation into a struct as a constexpr static method. More simply, you could leave the current constexpr as is and call it from a constexpr static method of a new struct.

struct StaticStruct {
    static constexpr float MyMin_constexpr (float a, float b) {
        return a<b?a:b;
    }
};

也请定义此值(即使它看起来无用!):

Also, define this (even though it looks useless!):

template<int>
using Void = void;

基本思想是 Void< i> 要求 i 是一个常量表达式。更精确地讲,以下lambda仅在某些情况下才具有适当的重载:

The basic idea is that Void<i> requires that i be a constant expression. More precisely, this following lambda will have suitable overloads only in certain circumstances:

auto l = [](auto ty)-> Void<(decltype(ty)::   MyMin_constexpr(1,3)   ,0)>{};
                                              \------------------/
                                               testing if this
                                               expression is a
                                               constant expression.

仅当自变量存在时,才可以调用 l ty 的类型为 StaticStruct ,如果我们感兴趣( MyMin_constexpr(1,3))是一个常量表达式。如果我们用非恒定参数替换 1 3 ,则通用lambda l 将通过SFINAE丢失该方法。

We can call l only if the argument ty is of type StaticStruct and if our expression of interest (MyMin_constexpr(1,3)) is a constant expression. If we replace 1 or 3 with non-constant arguments, then the generic lambda l will lose the method via SFINAE.

因此,以下两个测试是等效的:

Therefore, the following two tests are equivalent:


  • StaticStruct :: MyMin_constexpr(1,3)恒定表达式吗?

  • 可以通过 l(StaticStruct {})调用 l 吗?

  • Is StaticStruct::MyMin_constexpr(1,3) a constant expression?
  • Can l be called via l(StaticStruct{})?

很容易删除 auto ty 和上述lambda中的 decltype(ty)。但这会带来硬错误(在非恒定情况下),而不是很好的替代失败。因此,我们使用 auto ty 来获取替换失败(我们可以有效地检测到)而不是错误。

It's tempting to simply delete auto ty and decltype(ty) from the above lambda. But that will give a hard error (in the non-constant case) instead of a nice substitution failure. We therefore use auto ty to get substitution failure (which we can usefully detect) instead of error.

此下一步代码是很简单的事情,当且仅当 f (我们的通用lambda)时,返回 std:true_type
可以用 a StaticStruct )调用:

This next code is a straightforward thing to return std:true_type if and only if f (our generic lambda) can be called with a (StaticStruct):

template<typename F,typename A>
constexpr
auto
is_a_constant_expression(F&& f, A&& a)
    -> decltype( ( std::forward<F>(f)(std::forward<A>(a)) , std::true_type{} ) )
{ return {}; }
constexpr
std::false_type is_a_constant_expression(...)
{ return {}; }

接下来,演示其用法:

int main() {
    {
        auto should_be_true = is_a_constant_expression(
            [](auto ty)-> Void<(decltype(ty)::   MyMin_constexpr(1,3)   ,0)>{}
            , StaticStruct{});
        static_assert( should_be_true ,"");
    }
    {   
        float f = 3; // non-constexpr
        auto should_be_false = is_a_constant_expression(
            [](auto ty)-> Void<(decltype(ty)::   MyMin_constexpr(1,f)   ,0)>{}
            , StaticStruct{});
        static_assert(!should_be_false ,"");
    }
}






为了直接解决您的原始问题,我们可以先定义一个宏来节省重复:


To solve your original problem directly, we could first define a macro to save repetition:

(我没有测试过此宏,对于任何拼写错误都表示歉意。)

(I haven't tested this macro, apologies for any typos.)

#define IS_A_CONSTANT_EXPRESSION( EXPR )                \
     is_a_constant_expression(                          \
         [](auto ty)-> Void<(decltype(ty)::             \
              EXPR                         ,0)>{}       \
         , StaticStruct{})

在这个阶段,也许您可​​以简单地做:

At this stage, perhaps you could simply do:

#define MY_MIN(...)                                            \
    IS_A_CONSTANT_EXPRESSION( MyMin_constexpr(__VA_ARGS__) ) ? \
        StaticStruct :: MyMin_constexpr( __VA_ARGS__ )     :   \
                        MyMin_runtime  ( __VA_ARGS__ )

或者,如果您不信任编译器来优化 std :: true_type std :: false_type 通过?:,然后也许:

or, if you don't trust your compiler to optimize std::true_type and std::false_type through ?:, then perhaps:

constexpr
float MyMin(std::true_type, float a, float b) { // called if it is a constant expression
    return StaticStruct:: MyMin_constexpr(a,b);
}
float MyMin(std::false_type, float , float ) { // called if NOT a constant expression
    return                MyMin_runtime(a,b);
}

使用此宏:

#define MY_MIN(...)                                             \
  MyMin( IS_A_CONSTANT_EXPRESSION(MyMin_constexpr(__VA_ARGS__)) \
       , __VA_ARGS__)

这篇关于constexpr函数中的编译时或运行时检测的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆