【问题标题】:Printing the values of an instantiated structure without using the names of the fields in C++打印实例化结构的值而不使用 C++ 中的字段名称
【发布时间】:2016-10-21 03:28:44
【问题描述】:

我可以吗?

例如考虑以下结构:

struct bag {
     string fruit;
     string book;
     string money;
};

我想按顺序打印结构包实例的字段值,并获得如下输出:

apple
Computer Networking, A top-down Approach
100

但不使用字段的名称(水果、书籍和金钱)。任何帮助,将不胜感激。我知道的唯一信息是所有字段都是 C++ 字符串。

【问题讨论】:

  • 这是an XY problem。你要解决的真正问题是什么?不是,不是您在这里要问的问题,而是真正的问题,您认为其声称的解决方案就是您在这里要问的问题。
  • 我想做一个函数,它可以接收任何结构并以顺序形式打印它的值。
  • @RenatoSanhueza 你能详细说明“任何结构”吗?它真的必须是世界上的任何结构吗?或者您只是希望能够方便地为 you 声明的结构而不重复逻辑执行此操作?或者您是否也需要对其他地方定义的任意结构执行此操作?
  • @NirFriedman 好吧,我想让这个函数在我自己的结构上使用它。这就是为什么我在上面的问题中声明这些字段只是字符串。我感兴趣的结构只有字符串类型的字段。结构之间可以有不同数量的字段,并且字段的名称可以更改(显然),因为每个结构代表不同的事物。正如您推断的那样,如果我有更好的选择,我不想为每个结构创建一个打印功能。

标签: c++ string struct


【解决方案1】:

根据您在 cmets 中的说明:这在 C++ 中是不可能的。 C++does not have reflection。 C++ 不能以这种方式工作。

【讨论】:

    【解决方案2】:

    虽然C++没有反射,但是你可以用Boost.Hana制作自己的反射工具。这是一个完整的程序,它遍历结构的成员,打印它们的名称和值。

    Hana 需要符合 C++14 标准的现代编译器,这意味着最新版本的 Clang 或 GCC 6+ 是您目前唯一选择此代码的选项。

    编辑:此代码现在使用BOOST_HANA_ADAPT_STRUCT 而不是BOOST_HANA_ADAPT_ADT

    #include <boost/hana/adapt_struct.hpp>
    #include <boost/hana/for_each.hpp>
    #include <boost/hana/fuse.hpp>
    #include <string>
    #include <iostream>
    
    namespace hana = boost::hana;
    
    using std::string;
    
    struct bag {
        string fruit;
        string book;
        string money;
    };
    
    BOOST_HANA_ADAPT_STRUCT(bag, fruit, book, money);
    
    int main() {
    
        bag my_bag{ "Apple", "To Kill A Mockingbird", "100 doubloons" };
    
        hana::for_each(my_bag, hana::fuse([](auto member, auto value) {
            std::cout << hana::to<char const*>(member) << " = " << value << "\n";
        }));
    }
    

    输出:

    fruit = Apple
    book = To Kill A Mockingbird
    money = 100 doubloons
    

    【讨论】:

    • 这使用了字段的名称,与问题中的要求相反。它还非常不必要地引入了对一个非常重的库的依赖,即 Boost。
    • 1.它仅在 BOOST_HANA_ADAPT_ADT 宏调用中使用字段的名称,该名称可以位于结构定义的右下方。反映成员不需要名称。 2. 哈娜!= 提升。你只需要在 Hana 中使用你需要的东西。事实上,我相信这个例子根本不需要 Boost(所以在这种情况下你可以使用 Hana 而不需要 Boost)。
    • @Cheersandhth.-Alf 这真的不是一个不必要的依赖,你的解决方案不仅是UB,而且很难扩展,用户可能有一天醒来并决定他想要非字符串字段。他已经拥有的一些领域似乎并不是真正的字符串。如果你想在 C++ 中做认真的工作,无论如何你都将重写相当多的 Boost,所以你不妨尽早接受它作为一个依赖项。
    • 为了记录,你也可以使用BOOST_HANA_DEFINE_STRUCT一次定义结构和适配代码,这样就不用重复名称了。如果您无权访问结构定义,也可以使用BOOST_HANA_ADAPT_STRUCT 代替BOOST_HANA_ADAPT_ADT。所有信息in the tutorial.
    • 我添加了 an example in my answer 来展示这在没有任何严重的库依赖和没有聪明的宏的情况下是多么的微不足道。主要是因为这个答案已经收集了赞成票。这就像投票赞成使用核武器杀死苍蝇的建议:是的,它有效,但不实用。
    【解决方案3】:

    由于您知道所有字段都是std::string,并且您可能控制了聚合类,因此您可以在实践中简单地将其视为std::string 的原始数组:

    Bag x;
    static constexpr int n = sizeof(x)/sizeof(std::string);
    for( int i = 0; i < n; ++i )
    {
        cout << reinterpret_cast<std::string const*>( &x )[i] << "\n";
    }
    

    如果你知道有多少个字段,那么你可以静态断言类的总大小,从而保证编译器没有添加任何填充(除了第一个字段之前,它几乎可以在任何地方添加填充,这必须在偏移量 0):

    #define STATIC_ASSERT( e ) static_assert( e, #e " // <- must be true" )
    
    STATIC_ASSERT( sizeof( Bag ) == 3*sizeof( std::string ) );
    

    否则将其视为数组是形式上未定义的行为,但我认为找不到默认情况下会添加任何填充的编译器,因此它在实践中应该非常便携。

    上述免责声明:代码未经编译器审查。


    如果您想真正做到安全,甚至不是学究式的 UB,并且如果这超过了根本不使用项目名称的要求,那么只需在每个结构旁边声明迭代所需的知识,例如:

    #include <array>
    #include <string>
    using namespace std;
    
    struct Bag
    {
         string fruit;
         string book;
         string money;
    };
    
    array<string Bag::*, 3> const bag_items = {&Bag::fruit, &Bag::book, &Bag::money};
    
    #include <iostream>
    auto main()
        -> int
    {
        Bag const bag = { "Apple", "Zen and TAOMM", "billion bucks" };
        for( auto mp : bag_items )
        {
            cout << bag.*mp << "\n";
        }
    }
    

    对于异构结构(不同类型的项目),您会更自然地使用函数而不是直接成员指针。

    但是正如这表明的那样,即使完全害怕正式的 UB,也没有必要添加对像 Boost 这样的大型库的依赖:这样做是微不足道的。

    【讨论】:

    • downvote:这是一个非常糟糕的主意,因为它只是在寻找一些麻烦。当有人决定向结构中添加另一个非字符串成员时,祝您调试代码好运。
    • @LouisDionne:关于“当有人决定向结构中添加另一个非字符串成员时,祝您调试代码好运”,您似乎没有理解这里的任何内容,因为这没有任何意义。另外,您似乎没有阅读该问题。是你的反对票吗?如果是这样,请不要因为无知而投票。
    • 我对所有建议解决问题的方法的答案都投了赞成票,但我会接受这个答案,因为它不需要繁重的库的支持,也没有硬性约束编译器的使用。尽管如此,对于可能访问这篇文章的未来读者来说,探索其他答案也是值得的。我一直在阅读有关 Boost 的信息,它似乎是一个非常强大的工具。谢谢大家。
    • 很遗憾,这演变成了关于标准解释的争论。如果是 UB,那么即使对编译器来说也是一个糟糕的解决方案。如果不是 UB,那么对于任何理智的软件工程师来说,这都是一个糟糕的解决方案。这就是我投反对票的原因。用可行的方法回答问题很容易,但实际上教育发帖人了解最新技术更困难。
    • You 第一个例子是UB,正如一些人已经指出的那样。第二个示例甚至不符合问题的要求,尽管发布者似乎没有意识到这一点:打印应该在不使用字段名称的情况下进行。您对bag_items 的定义使用了所有字段的名称。如果我们要在每个结构的“旁边”声明一些东西,为什么不只声明一个进行打印的函数呢?
    【解决方案4】:

    如果您只想在自己定义的结构上使用它,您可以使用 Boost Fusion 来实现。你可以像这样声明你的结构:

    BOOST_FUSION_DEFINE_STRUCT(
    (your_namespace1)(your_namespace2),
        bag,
            (string, fruit),
            (string, book),
            (string, money)
    )
    

    使用丑陋的宏有点烦人,但归根结底,它会生成相同的结构,加上一大堆其他东西,至少没有重复。

    一旦你以这种方式声明了你的结构,它现在被认为是一个 Boost Fusion 序列,你可以使用 Boost Fusion 的各种工具对其进行操作,包括for_each,它允许你将一个通用函数应用于每个元素你的结构。

    #include <boost/fusion/include/for_each.hpp>
    
    bag b1;
    // populate bag
    boost::fusion::for_each(b1, [] (const auto& x) { std::cout << x << "\n";});
    

    这将打印出您行中的每个字段。你可以用这种方式很好地做各种事情。作为奖励,这将适用于非字符串字段而无需任何额外工作,因此您可以例如赚钱双倍(这可能更有意义,尽管我无法从问题中看出)。

    编辑:我看到那个发夹,我同时发布了。 Hana 是一个更新、更现代的库,它超越了 Boost Fusion 的功能(我相信)。如果你能用Hana,那无论如何都去吧,但是它对编译的一些要求是苛刻的,特别是如果你想以gcc为目标,世界上大部分人还是在5系列上。而且目前也无法使用许多其他编译器进行编译,afaik icc 和 msvc 根本不起作用。对于这样一个简单的问题,您可以使用 Fusion 很好地解决它,老实说,今天我会建议这样做。希望一年后我的建议会有所不同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-10
      • 2012-05-03
      相关资源
      最近更新 更多