将字符串映射到班级成员

时间:2018-12-20 20:29:47

标签: c++ c++11 c++14

说我有一个像下面这样的类/结构:

struct A {
    uint32_t a;
    uint8_t  b;
    uint16_t c;
};

我有一组与A的每个成员关联的字符串(可以是不同的整数类型,但不能是非整数类型,例如字符串),例如

"field1" -> A::a
"field2" -> A::b
"field3" -> A::c

假定字符串和成员之间始终存在1:1映射。有没有一种优雅的方法可以使用std :: unordered_map这样的方式将每个字符串映射到每个成员? 我希望能够使用字符串作为键来读取和写入每个字段,例如

A a {1,2,3};
mymap["field1"] = 4; // a.a = 4
mymap["field2"] = 5; // a.b = 5
auto c = mymap["field3"]; // c = a.c = 3

我正在使用C ++ 11/14,不能使用boost。


有关此问题的更多信息 : 问题中提到的struct A是程序的设置。它们是硬件配置参数,我的软件程序用于模拟硬件行为。这些设置/配置是像上面一样由脚本生成的struct。我们读/写这些struct成员,并且由于这些设置/配置的数量如此之多(几千),因此也能够方便地按其名称访问它们。这就是为什么&为什么我要将每个成员与一个字符串相关联。下标还是访问相应成员的函数并不重要,但是字符串(设置名称)与生成的struct成员之间存在1:1映射。正如我在问题中提到的那样,这些成员属于不同的整数类型。

5 个答案:

答案 0 :(得分:2)

template<class V>
struct pseudo_ref_t {
  operator V()&& { return getter(); }
  void operator=(V v)&&{
    setter(std::move(v));
  }

  std::function<void(V)> setter;
  std::function<V()> getter;
};
template<class T, class V>
struct member_t {
  friend pseudo_ref_t<V> operator->*( T* t, member_t const& self ) {
    return {
      [&self, t](V in){ self.setter(*t, std::move(in)); },
      [&self, t]()->V{ return self.getter(*t); }
    };
  }
  friend V operator->*( T const* t, member_t const& self ) {
    return self.getter(*t);
  }
  std::function<void(T&, V)> setter;
  std::function<V(T const&)> getter;
};
template<class T, class V, class X>
member_t<T, V> make_member( X T::* mem_ptr ) {
  return {
    [mem_ptr](T& t, V in) {
      (t.*mem_ptr) = std::move(in);
    },
    [mem_ptr](T const& t)->V {
      return (t.*mem_ptr);
    }
  };
}

member_t<A, uint32_t>可以对A的任何成员进行类型擦除,这些成员可以隐式地与uint32_t进行转换。

它就像一个聪明的成员指针。

答案 1 :(得分:2)

您可以创建一个环绕整数的代理类,然后将该代理类存储在std::unordered_map中。

#include <iostream>
#include <functional>
#include <unordered_map>

struct A {
    uint32_t a;
    uint8_t  b;
    uint16_t c;
};

struct ValueWrapper {
    using value_type = uint64_t;

    template <typename Obj, typename T>
    ValueWrapper(Obj& obj, T Obj::*member) {
        get = [&, member]() { return obj.*member; };
        set = [&, member](value_type value) mutable { obj.*member = value; };
    }

    ValueWrapper()  = default;

    ValueWrapper& operator=(value_type value) {
        set(value);
        return *this;
    }

    operator value_type() {
        return get();
    }

    std::function<value_type()> get;
    std::function<void(value_type)> set;
};

std::unordered_map<std::string, ValueWrapper> make_map(A& a) {
    std::unordered_map<std::string, ValueWrapper> map;

    map["field1"] = ValueWrapper(a, &A::a);
    map["field2"] = ValueWrapper(a, &A::b);
    map["field3"] = ValueWrapper(a, &A::c);

    return map;
}

int main() {
    A a{1,2,3};

    auto map = make_map(a);

    map["field2"] = 67;

    std::cout << a.a << " " << static_cast<int>(a.b) << " " << a.c << std::endl;
    std::cout <<  map["field1"] << " " <<  map["field2"] << " " <<  map["field3"] << std::endl;
}

您确实会受到一些限制,具体取决于value_type。如果您使用int64_t,则可以安全地包装uint64_t以外的任何东西。如果您使用uint64_t,则可以包装所有无符号整数,但不能安全地包装有符号整数。

我将默认构造函数放在那里以满足unordered_mapoperator[]的使用。

答案 2 :(得分:0)

您说过它可以具有不同的整数类型,但是要有一个优雅的解决方案是一个问题。如果可以设置为单个类型,这将很简单,如下所示。

 #include <iostream>
 #include <map>

 struct A {
     int a;
     int b;
     int c;
 };
 using namespace std;

 int main() {
     map<string, int A::*> abc = {
         {"a", &A::a},
         {"b", &A::b},
         {"c", &A::c}
     };

     A aa;

     aa.*abc["a"] = 1;
     aa.*abc["b"] = 2;
     aa.*abc["c"] = 3;

     cout << "a = " << aa.a << "(" << aa.*abc["a"] << ")" << endl;
     cout << "b = " << aa.b << "(" << aa.*abc["b"] << ")" << endl;
     cout << "c = " << aa.c << "(" << aa.*abc["c"] << ")" << endl;

     return 0;
 }

答案 3 :(得分:0)

您的问题的主要挑战是不同的成员具有不同的类型。 如果使类型相同,则可以使用很多技巧。

对于您的情况,我知道您不想使用Boost或C ++ 17,但是为了向您展示未来的挑战,让我为您提供Boost.Hana C ++ 17解决方案。

#define  BOOST_HANA_CONFIG_ENABLE_STRING_UDL
#include <boost/hana/equal.hpp>
#include <boost/hana/string.hpp>
#include <cstdint> // uint
#include <cassert>

struct A {
    uint32_t a;
    uint8_t  b;
    uint16_t c;
};

struct map{
    A& aa_;
    template<class String>
    decltype(auto) operator[](String s) const{
        using namespace boost::hana::literals;
        if constexpr(s == "field1"_s) return (decltype(aa_.a)&)(aa_.a);
        if constexpr(s == "field2"_s) return (decltype(aa_.b)&)(aa_.b);
        if constexpr(s == "field3"_s) return (decltype(aa_.c)&)(aa_.c);
    }
};

using namespace boost::hana::literals;

void f(uint32_t& a){ a = 3.;}

int main(){
    A aa{1,2,3};
    map mymap{aa};
    mymap["field1"_s] = 4; assert(aa.a == 4);
    mymap["field2"_s] = 5; assert(aa.b == 5);
    mymap["field3"_s] = 6; assert(aa.c == 6);
    auto c = mymap["field3"_s]; assert( c == aa.c );
    mymap["blabla"_s]; // is void (not a compile error)
    assert( map{aa}["field1"_s] == 4 );
}

由此,您可以倒退,也许可以弄清楚C ++ 14,面临的挑战是您必须实现自己的编译时字符串文字和相等性。 换句话说,重新实现自己的Hana字符串:https://www.boost.org/doc/libs/1_61_0/libs/hana/doc/html/namespaceboost_1_1hana_1_1literals.html

答案 4 :(得分:0)

简单的解决方案是,如果您有一个可以生成该结构的脚本,那么您可以让该脚本生成您的设置器。

从简单的功能界面开始。

struct A {
    uint32_t a;
    uint8_t  b;
    uint16_t c;
};

typedef std::function<void(A &,string)> SetterType

然后创建一个查找表

std::map<std::string,SetterType> Lookup;

,然后使用您的脚本为每个字段生成解析器和设置器;

void A_a(A & data, std::string input){
        data.a = std::stoi(input);
}

然后

Lookup["a"] = &A_a;

并像使用它

Lookup["a"]("10");

如果您无法修改生成脚本,那么也许可以使用第三方解析器,例如swigclang来读取您的结构并生成一个解析树,然后可以将其用于生成您的查找表。

或者仅使用已经将字符串映射到C ++的系统。一个C ++ JSON生成器。

https://nlohmann.github.io/json/