【问题标题】:is bit_cast without compiler support for constexpr memcpy possible?没有编译器支持 constexpr memcpy 的 bit_cast 可能吗?
【发布时间】:2020-02-17 00:47:23
【问题描述】:

我听说std::bit_cast 将在 C++20 中使用,我对实现它必然需要特殊编译器支持的结论感到有些困惑。

公平地说,我听到的论点是实现执行memcpy 操作,而memcpy 通常不是constexpr,而std::bit_cast 应该是,所以使std::bit_cast @987654328 @ 据说需要编译器支持 constexpr 兼容的 memcpy 操作。

不过,我想知道,是否可以在根本不实际调用 memcpy 的情况下实现兼容的 bit_cast(即定义的行为,其程度与使用 memcpy 的定义行为相同)。

考虑以下代码:

template<typename T, typename U> 
inline constexpr T bit_cast(const U & x) noexcept {
    static_assert(std::is_trivial<T>::value && std::is_trivial<U>::value, "Cannot use bit_cast with non-trivial data" );
    static_assert(sizeof(T) == sizeof(U), "bit_cast must be used on identically sized types");
    union in_out {
        volatile U in;
        volatile T out;

        inline constexpr explicit in_out(const U &x) noexcept : in(x)
        {
        }

    };
    return in_out(in_out(x)).out;
}

这里使用易失性成员来强制编译器发出必要的代码,这些代码将从成员中写入或读取,禁用优化,虽然我知道通常分配给联合的一个成员并从同一个联合中的另一个成员读取是未定义的行为,C++ 标准似乎允许从联合 IF 的任何成员读取它已从完全相同的联合的另一个实例按字节复制。在上面的代码中,这可以通过在恰好初始化in 数据成员的新构造实例上显式调用默认复制构造函数来有效地完成。由于上述联合包含所有普通类型,对其调用默认复制构造函数相当于按字节复制,因此从新构造实例的out 成员读取不应该仍然是未定义的行为,不是吗?

当然,我完全有可能在这里遗漏了一些非常明显的东西......我当然不能声称自己比制定这些标准的人更聪明,但是如果有人能准确地告诉我我有哪些未定义的行为我在这里调用,我真的很想知道它。

【问题讨论】:

    标签: c++ undefined-behavior constexpr c++20 type-punning


    【解决方案1】:

    在持续评估期间不允许做的事情之一是来自[expr.const]/4.9

    左值到右值的转换,应用于引用联合或其子对象的非活动成员的左值;

    你的实现是做什么的,所以它不是一个可行的实现策略。

    【讨论】:

    • 但我实际上并不是指“非活动”成员。这仅适用于我分配 in 字段的原始构造实例。然后我调用一个复制构造函数,它按字节复制整个联合,之后显然允许所有字段[basic.types]/3。标准规定调用memcpy,但调用复制构造函数应该做同样的事情,不是吗?
    • @markt1964 是的。您引用的部分与 memcpying T 有关,它与联合或突然将 T 视为 U 无关。复制构造函数不会神奇地改变任何东西(并且您的代码没有调用复制构造函数无论如何 - in_out(in_out(x))in_out(x) 的含义相同,因为 C++17)
    【解决方案2】:

    这里使用可变成员来强制编译器发出必要的代码

    如果您需要编写volatile 以确保编译器不会优化您的代码,那么您的代码就不行。编译器无法修改有效代码的可观察行为。如果您想做的事情(sans volatile)已经被定义为行为,那么编译器就不会被允许优化您想要使用 volatile 强制执行的写入和读取。

    UB 来自这样一个事实,即您只被允许读取工会的活动成员(在您的示例中为 in),但您读取的是非活动成员(在您的示例中为 out)。

    【讨论】:

    • 实际上,我从一个实例的out 成员中读取,该实例是从同一联合的另一个实例按位复制的,因此代码不会从“非活动”成员中读取。我似乎记得在某处读到过,该标准明确允许从已从同一类型的另一个实例按字节复制的联合的任何字段中读取,这就是我通过显式调用复制构造函数来有效地做的事情。
    • @markt1964 我不知道这样的规则。我知道一个工会只能有一个活跃的成员。 C 确实允许您使用联合进行类型双关,大多数编译器允许您在 C++ 中使用联合进行类型双关及其扩展,但在标准 C++ 中您不能。
    • @markt1964 这与您的情况无关。这是关于从同一类型到同一类型的内存复制。
    • 作为附录,我发现如果我没有使成员 volatile,如果对其中一种类型有对齐限制,并且启用了优化,它会在运行时产生错误。编译器基本上试图优化分配,只是试图通过联合传递来使用尚未正确对齐的原始数据。对于其他一切,它工作得很好。放入 volatile 会强制编译器使用联合并且数据正确对齐,即使对于具有严格限制的类型也是如此。
    • @markt1964:“它对我有用”在这里不相关。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-14
    • 1970-01-01
    • 2015-08-03
    • 2012-12-22
    • 1970-01-01
    • 2011-11-02
    • 1970-01-01
    相关资源
    最近更新 更多