说我有一个像下面这样的类/结构:
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映射。正如我在问题中提到的那样,这些成员属于不同的整数类型。
答案 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_map
对operator[]
的使用。
答案 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");
如果您无法修改生成脚本,那么也许可以使用第三方解析器,例如swig或clang来读取您的结构并生成一个解析树,然后可以将其用于生成您的查找表。
或者仅使用已经将字符串映射到C ++的系统。一个C ++ JSON生成器。