std::formatter C++ 20 与联合和结构

std::formatter c++ 20 with union and struct

提问人:Raffaello 提问时间:9/4/2023 最后编辑:timrauRaffaello 更新时间:10/1/2023 访问量:87

问:

有什么理由不使用这样的数据类型进行编译:std::format

#include <cstdint>

typedef union MIDI_EVENT_type_u
{
    uint8_t val;
    struct {
        uint8_t low : 4;
        uint8_t high : 4;
    };
} MIDI_EVENT_type_u;

用于打印该值仅适用于上述值,并且 和 不会编译,并且必须转换为无符号 int。std::formatuint8_tvallowhigh

有什么原因可以喂食吗?std::formatuint8_t

例如

MIDI_EVENT_type_u type = {0};
auto a = std::format("{}", type.val);
auto b = std::format("{}", type.high); // error
auto c = std::format("{}", type.low);  // error

错误是:

no instance of overloaded function "std::format" matches the argument list
argument types are: (const char [3], uint8_t)

在这种情况下,有没有办法编写自定义来自动将强制转换为未签名?std::formatter

此外,不清楚为什么即使被检测为 ,也不编译 “” 中的值,有什么原因吗?union.structuint8_t


外延:

在@user17732522的回答和评论之后(谢谢),对于这个 particulat 案例,有这样的解决方案(至少对于编译,不确定它是否会完全解决类型双关语):

在可能的情况下,将该结构更改为:

typedef union MIDI_EVENT_type_u
{
    uint8_t val;
    // the struct also give it a name and put private would be better.
    struct {
        uint8_t low : 4;
        uint8_t high : 4;
    };
    constexpr uint8_t getHigh() { return high; };
    constexpr uint8_t getLow() { return low; };

} MIDI_EVENT_type_u;

使用 and 方法将解决编译错误,甚至使用全局 constexpr 最终返回 .high 或 .low 值。getHigh()getLow()

在我看来,C++ 中缺少一些东西来处理这种情况,这是自 C '70 以来的基本用例......

ERGO:在这种情况下,C++ 也可以“自动生成”或默认执行类似 2 个额外 constexpr 的事情,在我看来,在编写代码时不需要如此冗长。


相反,如果这是一个无法修改的 C 结构:

typedef union MIDI_EVENT_type_u
{
    uint8_t val;
    struct {
        uint8_t low : 4;
        uint8_t high : 4;
    };
} MIDI_EVENT_type_u;
 constexpr uint8_t get(const uint8_t val) { return val; };

这样它也会编译 做“基本操作”看起来过于冗长,而以前总是在 C/C++ 中工作,我真的不明白这一点。std::format("{}", get(type.low));

这在现代 C++ 标准中是否发生了变化,并且曾经被支持?

如果是后者,则不应该在下一次 C++ 迭代中考虑支持此类基本情况并使编译器在如此简单的样板代码下生成,因为它可以通过 constexpr 获取类型并返回相同的类型来解决?

C++ 结构 C++20 联合 stdformat

评论

0赞 Raffaello 9/4/2023
注意:我已经“扩展”了原始问题,因为它已经成为一个需要理解的更广泛的主题,而不仅仅是为什么不与工会成员一起编译 std::format 的简单场景

答:

4赞 user17732522 9/4/2023 #1

std::format通过引用(特别是作为转发引用)获取其参数。

位字段不能通过引用传递。因此,您确实需要首先转换为某种类型的 prvalue,然后可以从中具体化一个临时对象,该对象可以将引用参数绑定到该对象,例如通过写入

std::format("{}", uint8_t{type.high});

或者,如果您不想重复该类型:

std::format("{}", decltype(type.high){type.high});

在 C++23 中,您可以使用新语法:auto

std::format("{}", auto{type.high});

使用位域时,标准库的很大一部分都会遇到这个问题。标准库通常通过转发引用来获取泛型参数。

位字段的行为与普通的类成员或对象不同,对它们来说几乎是二等的。我建议避免使用位域或编写返回值的访问器成员函数,以便您可以直接使用它们。如果这是一个 C API,那么我建议先围绕它编写一个合适的 C++ API。


此外,使用匿名的语法不是标准的 C++。在标准 C++ 中,A 可以是匿名的,但 A 不能。但是,编译器可能会在 C++ 中将其作为扩展支持,因为它是 C11 中的有效语法。structunionstruct

此外,根据标准,访问并导致未定义的行为,因为是工会的活跃成员,并且只能读取活跃成员。不能使用联合进行类型双关语。然而,编译器通常支持将其作为扩展,因为它在 C 中定义了行为,但即便如此,位字段的布局也是实现定义的,例如,位字段打包到字节中的顺序取决于实现,读取这些非活动成员的结果也是如此。type.hightype.lowval

此外,假设这不是 C API,在 C++ 中是完全多余的。就够了。typedef union MIDI_EVENT_type_u { /*...*/ } MIDI_EVENT_type_u;union MIDI_EVENT_type_u { /*...*/ };

评论

1赞 user17732522 9/4/2023
@Raffaello 否,只需将 的成员标记为 并为其添加一个成员函数,例如 ,然后使用MIDI_EVENT_type_uprivate:uint8_t getHigh() const { return high; }std::format("{}", type.getHigh());
1赞 user17732522 9/4/2023
@Raffaello 然后编写C++类/函数来包装所有 C 库以提供该接口。通常,这涉及一些较大的设计注意事项。例如,您还需要设计 C++ API,以便它正确使用 RAII,而 C API 无法使用。
1赞 user17732522 9/4/2023
@Raffaello 或者作为更快的解决方法,将该函数设为免费函数: 然后 .但这感觉不是很干净。uint8_t getHigh(const MIDI_EVENT_type_u& x) { return x.high; }std::format("{}", getHigh(type));
1赞 user17732522 9/4/2023
@Raffaello 什么是“constexpr”?你是说函数吗?如果是这样,是的,声明函数是有意义的,但它对于优化来说可能并不重要。对于优化来说,重要的是该函数在使用它的每个翻译单元中定义,即它在标头中定义为(在类定义中隐含或在类定义中定义时)。constexprconstexprinlineconstexpr
2赞 user17732522 9/4/2023
@Raffaello 但是,C++20 允许您将对象的对象表示(即内存中的位)重新解释为不同类型的另一个值。与使用联合或违反别名的构造的类型双关语相比,它根据标准具有明确定义的行为,以及对象表示的实现定义性所施加的通常限制。它也只能用于这种重新解释有意义的类型(即简单可复制的类型,这在 C 中不是必需的限制)。std::bit_castreinterpret_cast