Reputation: 1780
Is there a way to enumerate the members of a structure (struct | class) in C++ or C? I need to get the member name, type, and value. I've used the following sample code before on a small project where the variables were globally scoped. The problem I have now is that a set of values need to be copied from the GUI to an object, file, and VM environment. I could create another "poor man’s" reflection system or hopefully something better that I haven't thought of yet. Does anyone have any thoughts?
EDIT: I know C++ doesn't have reflection.
union variant_t {
unsigned int ui;
int i;
double d;
char* s;
};
struct pub_values_t {
const char* name;
union variant_t* addr;
char type; // 'I' is int; 'U' is unsigned int; 'D' is double; 'S' is string
};
#define pub_v(n,t) #n,(union variant_t*)&n,t
struct pub_values_t pub_values[] = {
pub_v(somemember, 'D'),
pub_v(somemember2, 'D'),
pub_v(somemember3, 'U'),
...
};
const int no_of_pub_vs = sizeof(pub_values) / sizeof(struct pub_values_t);
Upvotes: 8
Views: 7346
Reputation: 76724
The following code does the job.
For large structs, you may need to increase the template recursion depth, e.g.:
nvcc: -ftemplate-depth 1000
gcc: -ftemplate-depth=1000
#include <memory>
#include <type_traits>
#ifndef CUDACC //disable __host__/__device__ prefixes
#define __host__
#define __device__
#endif
namespace detail {
template <class T, typename Member> class IsMemberConst {
private:
// T is not aggregate, otherwise `T{Detector{}}` must call aggregate
// initialization. However, `T{Detector{}}` actually called copy constructor
// instead
// //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
static_assert(!std::is_same<Member, std::remove_const_t<T>>::value,
"T must be aggregate "
"(https://en.cppreference.com/w/cpp/language/aggregate_initialization). "
"It's not allowed to have "
"(1) private/protected data member "
"(2) user-defined constructor "
"(3) base class "
"(4) virtual function ");
// if Member is not move constructible,
// `Member member = Detector{};` is not valid
static_assert(std::is_move_constructible<Member>::value, "All non-static data members must be move constructible");
// Note: standard layout type requires that all non-static data members must
// be standard layout type. For the following structure,
//
// struct T { T1 t1; T2 t2; };
//
// If T1 or T2 is not standard layout type, then T isn't standard layout
// type. However, since standard guarantees that (void*)&t1 < (void*)&t2. It
// does not make much sense for compiler to add more than necessary padding
// between adjacent data members (even it is allowed to do so). So in real
// world, I believe it should still work if we remove the following
// static_assert.
//static_assert(std::is_standard_layout<Member>::value,
// "All non-static data members must be standard layout type "
// "(https://en.cppreference.com/w/cpp/concept/"
// "StandardLayoutType), otherwise T is not standard layout");
// T can not have data member that has template converting function like
// this struct Member {
// Member() {}
// template<class U> Member(U&&) {}
// };
// Otherwise `Member member = Detector{};` is ambiguous since it could call
// both `Member::Member(Detector)` and `Detector::operator Member()`.
struct SimpleDetector {
template <class U>
__host__ __device__ /* implicit */ operator U();
};
static int dummyCheck(Member);
static_assert(
sizeof(dummyCheck(SimpleDetector{})),
"All non-static data members must not have template converting "
"constructor "
"(https://en.cppreference.com/w/cpp/language/converting_constructor)");
public:
// If T is const or T has non-static const data member, we want to forward
// member to callback function as const reference. Otherwise we could just
// forward as reference so that the callback could modify the input.
//
// so, how to check whether T has non-static const data member?
// 1) If T has const data member, it will not be move assignable by default.
// 2) If T has user-defined move assignment, it won't be move constructible.
// 3) If T has user-defined move constructor, it can't be aggregate.
static constexpr bool value =
std::is_const<T>::value ||
!std::is_move_assignable<T>::value ||
!std::is_move_constructible<T>::value;
};
template <class T, class MemberTypeCallback> struct Detector {
MemberTypeCallback& memberTypeCallback;
template <class Member, bool kIsConst = IsMemberConst<T, Member>::value>
__host__ __device__ /* implicit */ operator Member() {
memberTypeCallback(
static_cast<std::conditional_t<kIsConst, const Member*, Member*>>(
nullptr));
return {};
}
template <int... I>
__host__ __device__ void call(...) {
// T is not aggregate, otherwise `T{Detector{}}` is valid or T is empty
static_assert(
sizeof...(I) > 0 || std::is_empty<T>::value,
"T must be aggregate (https://en.cppreference.com/w/cpp/language/"
"aggregate_initialization). "
"It's not allowed to have "
"(1) private/protected data member "
"(2) user-defined constructor "
"(3) base class "
"(4) virtual function");
// This is aggregate initialization
// (https://en.cppreference.com/w/cpp/language/aggregate_initialization),
// C++ Standard guarantees that the arguments will be evaluated
// sequentially (https://en.cppreference.com/w/cpp/language/eval_order).
// Detector has "operator U();", which means Detector has implicit
// conversion to any type. In order to initialize T, the compiler will
// call "Detector::operator U()" with T's member type in memory layout's
// order, which means we could record type of each member and use it to
// compute the offsets.
T{ (I, *this)... };
}
template <int... I>
__host__ __device__ auto call(int) -> decltype(T{ *this, (I, *this)... }) {
call<0, I...>(0);
return {};
}
};
template <class T, class Callback>
__host__ __device__ void foreachMemberType(Callback&& callback) {
Detector<std::remove_reference_t<T>, decltype(callback)>{callback}.call(0);
}
} // namespace detail
template <class T, class Callback>
__host__ __device__ void foreachMember(T&& t, Callback&& callback) {
detail::foreachMemberType<T>(
[&t, &callback, lastUnusedOffset = 0](auto* dummyMember) mutable {
using Member = std::remove_reference_t<decltype(*dummyMember)>;
constexpr auto memAlignment = alignof(Member);
// `offset` is the minimum number which satisfies
// 1. offset >= lastUnusedOffset
// 2. offset % memAlignment == 0
const auto offset = (lastUnusedOffset + memAlignment - 1) / memAlignment *
memAlignment;
lastUnusedOffset = offset + sizeof(Member);
const char* addr = static_cast<const char*>(
static_cast<const void*>(std::addressof(t)));
const Member& member = *static_cast<const Member*>(
static_cast<const void*>(addr + offset));
// It is possible to provide such interface:
// callback(Member&& member, std::integral_constant<int, pos>);
// though I am not sure whether it's useful.
callback(const_cast<Member&>(member));
});
// This should be no-op in C++14 since if T is aggregate and all non-static
// data members are standard layout type, T should be standard layout type.
// However, this is not the case in C++17. In C++17, aggregate could have
// base type. Without this check, we don't know whether compiler performed
// empty base optimization.
//static_assert(std::is_standard_layout<std::remove_reference_t<T>>::value);
static_assert(!std::is_volatile<std::remove_reference_t<T>>::value);
}
Example usage:
//!struct Child: Parent { //base class not allowed
struct X_t {
X_t() = default;
int A;
uint64_t B;
//!private: not allowed
//!protected: not allowed
//!virtual: not allowed
//!X_t() {} //custom constructor not allowed
//!int B = 10; default initialization not allowed
};
...
X_t X;
//example with printf, because CUDA does not have std::cout, nor std::format
foreachMember(T&& t, foreachMember(*this, [](auto&& v){
uint64_t data = 0;
const auto size = sizeof(v);
if constexpr (size == sizeof(uint32_t)) { data = std::bit_cast<uint32_t>(v); }
if constexpr (size == sizeof(uint64_t)) { data = std::bit_cast<uint64_t>(v); }
printf("data = %llu,", data);
});
Upvotes: 0
Reputation: 12175
Since C++ does not have reflection builtin, you can only get the information be teaching separately your program about the struct content.
This can be either by generating your structure from a format that you can use after that to know the strcture information, or by parsing your .h file to extract the structure information.
Upvotes: 0
Reputation: 13973
You can specify your types in an intermediate file and generate C++ code from that, something like COM classes can be generated from idl files. The generated code provides reflection capabilities for those types.
I've done something similar two different ways for different projects:
Upvotes: 3
Reputation: 2532
simplest way - switch to Objective-C OR Objective-C++. That languages have good introspection and are full-compatible with C/C++ sources.
also You can use m4/cog/... for simultaneous generation structure and his description from some meta-description.
Upvotes: 2
Reputation: 133128
It feels like you are constructing some sort of debugger. I think this should be doable if you make sure you generate pdb files while building your executable.
Not sure in what context you want to do this enumeration, but in your program you should be able to call functions from Microsofts dbghelp.dll to get type information from variables etc. (I'm assuming you are using windows, which might of course not be the case)
Hope this helps to get you a little bit further.
Cheers!
Upvotes: 0
Reputation: 84229
Boost has a ready to use Variant library that may fit your needs.
Upvotes: 1
Reputation: 158484
To state the obvious, there is no reflection in C or C++. Hence no reliable way of enumerating member variables (by default).
If you have control over your data structure, you could try a std::vector<boost::any>
or a std::map<std::string, boost::any>
then add all your member variables to the vector/map.
Of course, this means all your variables will likely be on the heap so there will be a performance hit with this approach. With the std::map approach, it means that you would have a kind of "poor man's" reflection.
Upvotes: 4