Closed SanPen closed 1 year ago
Hi Santiago,
Using fields by name and aliases is something I've done as part of Json, but there is not yet support in the reflection part of the library (hopefully I'll have something there soon). In the meantime, I've pulled some code out you could use: https://godbolt.org/z/xnKjd1Gq4
There is a theoretical problem with making convenient getters for reflected fields in C++: ideally you'd be able to go...
auto x = get(obj, fieldIndex)
(or auto x = get(obj, fieldName)
)
with a runtime fieldIndex (or field name), but compilers require return types to be know at compile time, so you need to supply the type explicitly, return a variant (which only pushes the problem of using the value back a step), or use a visitor - which is why this library heavily uses lambda-visitor methods. Some example of what you can do...
float capacity = Fields::get<float>(fuelTank, "capacity");
Fields::visit(fuelTank, "capacity", [&](auto & value) { /* do stuff with value */ });
Setters don't need to return the value, so they can be done easily
Fields::set(fuelTank, "currentLevel", 2.1);
I've also put an example of aliasing a field using notes in the same godbolt.
Hope this helps!
Hi Justin,
The addition of Fields::set
and Fields::get
looks like what I need.
The Way I added the FieldsCache
and the rest of your code from the example to my code was to extend Reflect.h
with this:
// this goes after the ObjectMapper namespace
namespace Reflect::Fields {
template <typename T>
struct FieldCache {
struct NameIndex {
std::string name;
int index = 0;
};
static inline std::hash<std::string> strHash;
static inline std::multimap<size_t, NameIndex> fieldHashToIndex = [](){
std::multimap<size_t, NameIndex> fieldCache{};
class_t<T>::ForEachField([&](auto & field) {
using Field = std::remove_reference_t<decltype(field)>;
if constexpr ( Field::template HasAnnotation<Alias> )
{
const auto & fieldAlias = field.template getAnnotation<Alias>().value;
NameIndex nameIndex { std::string(fieldAlias), field.Index };
fieldCache.insert(std::make_pair(strHash(std::string(fieldAlias)), nameIndex));
}
NameIndex nameIndex { field.name, field.Index };
fieldCache.insert(std::make_pair(strHash(std::string(field.name)), nameIndex));
});
return fieldCache;
}();
static inline size_t getFieldIndex(const std::string & fieldName) {
size_t fieldIndex = std::numeric_limits<size_t>::max();
size_t fieldNameHash = strHash(fieldName);
auto fieldHashMatches = fieldHashToIndex.equal_range(fieldNameHash);
for ( auto it = fieldHashMatches.first; it != fieldHashMatches.second; ++it )
{
if ( it->second.name.compare(fieldName) == 0 )
fieldIndex = it->second.index;
}
return fieldIndex;
}
};
template <typename T>
struct TypeName {
static const char* Get() {
return typeid(T).name();
}
};
template <typename T, typename Obj>
struct FieldNotFound : std::exception {
std::string msg;
FieldNotFound(const std::string & fieldName) {
// msg = std::string("Field \"") + fieldName + "\" of type \"" + TypeToStr<T>() + "\" not found in \"" + TypeToStr<Obj>() + "\""; // TODO this is C++20
msg = std::string("Field \"") + fieldName + "\" of type \"" + TypeName<T>() + "\" not found in \"" + TypeName<Obj>() + "\"";
}
virtual const char* what() const noexcept {
return msg.c_str();
}
};
template <typename T, typename Obj>
const T & get(const Obj & obj, const std::string & fieldName) {
size_t fieldIndex = FieldCache<Obj>::getFieldIndex(fieldName);
const T* valuePtr = nullptr;
if ( fieldIndex != std::numeric_limits<size_t>::max() ) {
class_t<Obj>::FieldAt(fieldIndex, [&](auto & field){
using FieldType = typename std::remove_reference_t<decltype(field)>::Type;
if constexpr ( std::is_same_v<FieldType, T> )
valuePtr = &(obj.*field.p);
});
}
if ( valuePtr != nullptr ) {
return *valuePtr;
} else {
throw FieldNotFound<T, Obj>{fieldName};
}
};
template <typename Obj, typename Function>
void visit(Obj & obj, const std::string & fieldName, Function function) {
size_t fieldIndex = FieldCache<Obj>::getFieldIndex(fieldName);
if ( fieldIndex != std::numeric_limits<size_t>::max() ) {
class_t<Obj>::FieldAt(obj, fieldIndex, [&](auto & field, auto & value) {
function(value);
});
}
}
template <typename T, typename Obj>
void set(Obj & obj, const std::string & fieldName, const T & value) {
size_t fieldIndex = FieldCache<Obj>::getFieldIndex(fieldName);
if ( fieldIndex != std::numeric_limits<size_t>::max() ) {
class_t<Obj>::FieldAt(obj, fieldIndex, [&](auto & field, auto & fieldValue) {
ObjectMapper::map(fieldValue, value);
});
}
}
}
When using Fields::get
I'm having the following error:
Reflect.h:1027:51: error: ‘struct ns::CalculationNode::Class’ is private within this context
where CalculationNode
is the class I'm reflecting.
Not sure how to fix this. Also tried to replicate the bug in godbolt and I got an entirely different issue.
Any idea?
While I can't see your code, I'm guessing in CalculationNode need to ensure the REFLECT macro is at the end of the class in a section marked "public:" (you can mark multiple sections public as needed).
Bingo, It works.
I've run a couple of tests in ubuntu and macos and all goes well.
Thanks a lot.
Would you like me to send you the version of Reflect.h I have? I did a couple of corrections
You can place it on a fork, though it's unlikely I'll have much use for it (the code I gave you won't work for all fields since it's based on pointers and reference fields have no proper field pointer, and there's a lot of refactoring that will go into making it work more generally).
Accessing fields by names is part of release 2.0.0 e.g. https://godbolt.org/z/7zqnW6hnT , getters and setters won't get first-class support per design decisions
Hi!
I was looking for some piece of code to have in C++ something similar to setattr / getattr from python, and you library seems to be it.
I've read the readme a couple of times and maybe you could clarify the following questions I have:
a) Can I get the value of an attribute by its name without iterating the fields?
b) The
NOTE
macro seems very nice as well, can I use it to do the following?Thanks in advance, Santiago