在 Constexpr 函数中使用 Reinterpret_Cast [dcl.constexpr]/6 和 /7

问题描述

据我所知,C++11 特别指定 reinterpret_cast 不能在常量表达式中使用。原因(再次以我的理解)是编译器无法解释转换的有效性。话虽如此,似乎确实存在某种程度的技巧,即使在使用 reinterpret_cast 语句时,也可以使用该技巧来编译该函数

我有一种情况,可以根据当时我希望数据表示哪个子类来重新解释父类中的单个字节数组。

代码中,我有一个 constexpr,它返回对数组中子类成员变量表示的引用,在本例中为 uint32_t 变量。使用 reinterpret_cast<uint32_t&>() 代码不会编译,编译器声明 reinterpret_cast 不能产生常量表达式。但是,我可以通过将函数包装在模板中或使用简单的三元表达式来编译代码

下面的示例代码包含一个标记compBranchSwitch 的宏,它允许您为方便起见在编译方案之间快速切换。

#include <cstdint>
#include <cstddef>
#include <array>
#include <iostream>

#define compBranchSwitch 0          //Switch to determine which branch to compile: 2 - With template function,1 - With ternary operator,0 - Without any trickery (should not compile)

struct Attributes {
    static std::array<char,4> membersArray;

    struct Subclass {
        uint32_t num;

        static constexpr uint16_t offsetNum() { return offsetof(Subclass,num); }

#if compBranchSwitch == 2
        template<bool nothing>      //Unused template parameter that circumvents reinterpret_cast being unusable within a constexpr.
        static constexpr uint32_t& LoadNum() { return reinterpret_cast<uint32_t&>(membersArray[offsetNum()]); }

#elif compBranchSwitch == 1
        static constexpr uint32_t& LoadNum() { return (true ? reinterpret_cast<uint32_t&>(membersArray[offsetNum()]) : reinterpret_cast<uint32_t&>(membersArray[offsetNum()])); }

#else
        static constexpr uint32_t& LoadNum() { return reinterpret_cast<uint32_t&>(membersArray[offsetNum()]); }
#endif

        static inline void SaveNum(const uint32_t& newTest) { std::memcpy(&membersArray[offsetNum()],&newTest,sizeof(newTest)); }
    };
};

std::array<char,4> Attributes::membersArray;

void main() {

    Attributes::Subclass::SaveNum(32);

#if compBranchSwitch == 2
    std::cout << Attributes::Subclass::LoadNum<true>();
#else
    std::cout << Attributes::Subclass::LoadNum();
#endif
}

我的问题是:

  • 我是否应该担心或犹豫使用上述任何技巧来编译程序?
  • 有没有更好的办法让 reinterpret_cast 在常量表达式中工作?
  • 仅仅因为常量表达式中不允许使用 reinterpret_cast,编译器是否仍可能在编译时在大量优化标志下对其进行评估?

如果有帮助,我正在 C++17 下编译并使用 Visual Studio

我发现有关 stackoverflow 的一篇密切相关的帖子对有关常量表达式的 C++11 草案和发现三元运算符技巧 can be found here 的信息很有帮助。

解决方法

首先是编译器can execute a function at compile-time even if it's not constexpr,只要它不影响程序的可见行为。相反,它可以在运行时执行 constexpr 函数,只要在编译时不需要知道其结果即可。

既然您说您不知道如何测试您的函数在编译时是否可调用,那么在我看来您只是添加 constexpr 以使您的代码更快。你不需要这样做,它可能不会因为我上面说的而改变任何东西。

至于你使用的技巧,它们没有任何用处。

constexpr 并不意味着该函数可以总是在编译时执行。这意味着它可以在编译时执行某些参数值(函数或模板参数)。

示例:

constexpr int foo(bool x) // The function compiles.
{
    if (x)
        return true;
    else
        return rand();
}

constexpr int a = foo(true); // Ok.
constexpr int b = foo(false); // Error.
int c = foo(false); // Ok.

编译器不需要严格验证至少存在一个合适的参数(因为它是 impossible in general)。

这就是您的代码中发生的事情。当编译器确定没有参数使其在编译时可调用时,编译器拒绝constexpr。当它不确定时,它让它滑动。但是当函数被实际调用时,很明显它的结果实际上并不是 constexpr,它的 constexpr 性被默默地忽略了(见上面的例子)。

由于没有允许您的函数在编译时执行的可能参数,您的代码是格式错误的 NDR。 NDR(“不需要诊断”)意味着编译器不需要注意到这个错误,不同的编译器对此可能或多或少的严格。

以下是标准的相关部分:

[dcl.constexpr]/6/7

6 — 对于既不是默认值也不是模板的 constexpr 函数或 constexpr 构造函数,如果不存在参数值使得函数或构造函数的调用可以是核心常量表达式的计算子表达式,或者对于构造函数,某个常量初始化对象 ([basic.start.static]) 的初始化全表达式的计算子表达式,程序格式错误,无需诊断。

7 — 如果 constexpr 函数模板或类模板的成员函数的实例化模板特化不能满足 constexpr 函数的要求,则该特化仍然是 constexpr 函数,即使对此类函数的调用不能出现在一个常量表达式中。 如果模板的特化不满足 constexpr 函数作为非模板函数的要求,则模板格式错误,无需诊断。

或者,简单来说: