【问题标题】:Using Reinterpret_Cast in a Constexpr Function在 Consexpr 函数中使用 Reinterpret_Cast
【发布时间】:2021-09-20 10:44:13
【问题描述】:

据我了解,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 的信息很有帮助。

【问题讨论】:

  • 所有这些花哨的技巧都可以让您将函数标记constexpr。但是您是否检查过是否可以在编译时调用它?我打赌不会。
  • @HolyBlackCat num 变量的值无法在编译时评估,因为 membersArray 不是常量。在编译时应该能够评估的是对membersArray 中的num 变量的引用或指针,这就是我要返回的内容。有没有一种简单的方法可以检查这是否在编译时真正被评估?
  • @HolyBlackCat 正在执行的membersArray 是静态的,并且没有任何调用引用实例化对象。如果我将Attributes 设为静态,会发生什么变化?
  • 抱歉,没注意到。那么是的,它不应该改变任何东西。一会儿我会发布一个正确的答案。
  • @Ryoku 那为什么不使用std::variant:en.cppreference.com/w/cpp/utility/variant

标签: c++ c++11 constexpr reinterpret-cast constant-expression


【解决方案1】:

首先,编译器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(“不需要诊断”)意味着编译器不需要注意这个错误,不同的编译器可以或多或少的严格。

这是标准的相关部分:

[dcl.constexpr]/6/7

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

7 — 如果 constexpr 函数模板或类模板的成员函数的实例化模板特化无法满足 constexpr 函数的要求,则该特化仍然是 constexpr 函数,即使调用此类函数不能出现在常量表达式中。 如果模板的特殊化在被视为非模板函数时不能满足 constexpr 函数的要求,则该模板是非良构的,不需要诊断。

或者,简单来说:

【讨论】:

  • 谢谢。我很好奇的一件事是,鉴于reinterpret_cast 的性质,将constexpr 标记放在loadNum 函数上是否可能有好处?从理论上讲,reinterpret_cast 实际上并没有对数据做任何事情,只是本质上告诉编译器我们将暂时将其视为一种新的数据类型,而 offsetof 只是一个应该在编译时解析的宏。我的直觉是num数据在堆栈上数组中的位置地址在编译时就已经知道并在机器代码中使用
  • @Ryoku 是的,如果优化器足够聪明,它可能会在编译时计算出来,但同样,添加 constexpr 并没有做任何事情(除了使你的代码格式错误的 NDR,因为技巧)。我试图在上面解释原因。
猜你喜欢
  • 1970-01-01
  • 2019-01-23
  • 2021-12-11
  • 2014-02-24
  • 2020-09-25
  • 1970-01-01
  • 1970-01-01
  • 2018-02-17
  • 1970-01-01
相关资源
最近更新 更多