在 Constexpr 函数中使用 Reinterpret_Cast

Using Reinterpret_Cast in a Constexpr Function

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

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

在代码中,我有一个 constexpr,其中 returns 是对数组中 subclasses 成员变量表示的引用,在本例中是 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
}

我的问题是:

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

在 Whosebug 上的一个密切相关的 post 我发现有关 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,并且它的 constexprness 被默默地忽略了(见上面的例子)。

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

以下是标准的相关部分:

[dcl.constexpr]/6 and /7

6 — For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression, or, for a constructor, an evaluated subexpression of the initialization full-expression of some constant-initialized object ([basic.start.static]), the program is ill-formed, no diagnostic required.

7 — If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function, that specialization is still a constexpr function, even though a call to such a function cannot appear in a constant expression. If no specialization of the template would satisfy the requirements for a constexpr function when considered as a non-template function, the template is ill-formed, no diagnostic required.

或者,简单来说: