-
Notifications
You must be signed in to change notification settings - Fork 6
1.2. Overview: How To...
Justin F edited this page Dec 12, 2023
·
22 revisions
Reflecting Members:
- Define, iterate & modify class members
- Access class members by constexpr index
- Access class members by runtime index
- Access class members by name
- Get member access protection modifier
- Get member metadata using identifier
- Pack & fold class members
- Filter members using constexpr ifs
- Filter members using predicate filtering
Alternative reflection methods:
- Reflect members outside of class
- Reflect private members outside of class
- Reflect private members outside of class with notes
- Auto-reflect members of aggregates
Notes / Annotations:
Reflecting Inheritance:
- Define & iterate super-classes
- Access super-class by constexpr index
- Access super-class by runtime index
- Traversing inheritance trees
Reflecting Functions:
Reflecting Overloads:
- Define, iterate & call overloads
- Access known overload by constexpr index
- Access known overload by runtime index
- Pack known overloads
- Access unknown overload (non-noted argument set)
- Working with cv-ref qualified overloads
Misc:
- Type toStr
- Runtime to constexpr index
- White-box testing
- Making tuples
- Tuplifying using wrapper type
- Directly tuplifying reflected types
Adaptive Structures:
- Clone reflected structure
- Typedefs per reflected structure member
- Implementing whiteboxer/reference structure
- Implementing builders
- Implementing change-listeners
RareBuilder:
RareMapper:
RareJson:
class MyObj
{
int myInt = 3;
char myChar = 'A';
public:
REFLECT(MyObj, myInt, myChar)
};
int main()
{
MyObj myObj{};
// Supplying an instance of MyObj, incrementing and printing the values
RareTs::Members<MyObj>::forEach(myObj, [&](auto member, auto & value) {
std::cout << member.name << " = " << ++value << std::endl;
});
// Same thing, but providing the instance of MyObj to member to get the value
RareTs::Members<MyObj>::forEach([&](auto member) {
std::cout << member.name << " = " << ++member.value(myObj) << std::endl;
});
// Similar, but only accessing the value of the member/no metadata like member name is available
RareTs::Values<MyObj>::forEach(myObj, [&](auto & value) {
std::cout << ++value << std::endl;
});
// Note: const correct, value is const if the member declaration or object instance is const
const MyObj & constRef = myObj;
RareTs::Values<MyObj>::forEach(constRef, [&](auto & value) {
static_assert(std::is_const_v<std::remove_reference_t<decltype(value)>>, "const check");
});
}
- intro to REFLECT macro
- intro to RareTs::Members
- detail document on REFLECT macro
- detail document on Members
class MyObj
{
int myInt = 3;
char myChar = 'A';
public:
REFLECT(MyObj, myInt, myChar)
};
int main()
{
MyObj myObj{};
using ZerothMember = RareTs::Member<MyObj, 0>;
using FirstMember = RareTs::Member<MyObj, 1>;
std::cout << "[" << ZerothMember::index << "] " << RareTs::toStr<typename ZerothMember::type>()
<< " " << ZerothMember::name << " = " << ZerothMember::value(myObj) << std::endl;
std::cout << "[" << FirstMember::index << "] " << RareTs::toStr<typename FirstMember::type>()
<< " " << FirstMember::name << " = " << FirstMember::value(myObj) << std::endl;
}
- intro to REFLECT macro
- intro to RareTs::Member
- detail document on REFLECT macro
- detail document on Member
class MyObj
{
int myInt = 3;
char myChar = 'A';
public:
REFLECT(MyObj, myInt, myChar)
};
int main()
{
std::cout << "Enter the index of the member you wish to get: ";
size_t memberIndex = 0;
std::cin >> memberIndex;
if ( !std::cin || memberIndex > RareTs::Members<MyObj>::total ) {
throw std::logic_error("Invalid index!");
}
MyObj myObj{};
// Not supplying an instance of MyObj
RareTs::Members<MyObj>::at(memberIndex, [&](auto & member) {
std::cout << "[" << member.index << "] " << member.name << " = " << member.value(myObj) << std::endl;
});
// Supplying an instance of MyObj
RareTs::Members<MyObj>::at(memberIndex, myObj, [&](auto & member, auto & value) {
std::cout << "[" << member.index << "] " << member.name << " = " << value << std::endl;
});
}
- intro to REFLECT macro
- intro to RareTs::Members
- detail document on REFLECT macro
- detail document on Members
struct MyObj
{
int myInt = 0;
long myLong = 0;
char myChar = '0';
REFLECT(MyObj, myInt, myLong, myChar)
};
void printObj(const MyObj & myObj) {
std::cout << "{ " << "myInt:" << myObj.myInt << ", myLong:" << myObj.myLong << ", myChar:" << myObj.myChar << " }" << std::endl;
}
template <typename T>
constexpr auto parseMember(T & obj, std::string_view memberInput)
{
size_t colon = memberInput.find_first_of(":");
std::string_view name = memberInput.substr(0, colon);
std::string_view newValue = memberInput.substr(colon+1, memberInput.size());
RareTs::Values<T>::named(name, obj, [&](auto & value) {
value = std::remove_reference_t<decltype(value)>(int(newValue[0]-'0')*10+int(newValue[1]-'0'));
});
}
constexpr auto parseInput(std::string_view input)
{
MyObj myObj {};
size_t commaIndex = input.find_first_of(",");
parseMember(myObj, input.substr(0, commaIndex));
parseMember(myObj, input.substr(commaIndex+1, input.size()));
return myObj;
}
int main()
{
constexpr std::string_view input = "myInt:77,myChar:99";
constexpr auto myConstexprObj = parseInput(input); // Makes use of access by constexpr member name from "input"
std::cout << "constexpr input \"" << input << "\" parsed to constexpr object: ";
printObj(myConstexprObj);
std::string memberName {};
std::cout << "Enter a member name: ";
std::cin >> memberName;
RareTs::Values<MyObj>::named(memberName, myConstexprObj, [&](auto & value) { // Makes use of runtime-provided member name
std::cout << "Member " << memberName << " had value: " << value << std::endl;
});
}
- intro to REFLECT macro
- intro to RareTs::Members
- detail document on REFLECT macro
- detail document on Members
class MyObj
{
public:
int pub = 0;
protected:
int prot = 0;
inline static int otherProt = 0;
private:
int priv = 0;
public:
int otherPub = 0;
REFLECT(MyObj, pub, prot, otherProt, priv, otherPub)
};
void printNonPrivateMods(size_t memberIndex, RareTs::AccessMod accessMod)
{
if ( accessMod == RareTs::AccessMod::Public )
std::cout << "member " << memberIndex << " is public" << std::endl;
else if ( accessMod == RareTs::AccessMod::Protected )
std::cout << "member " << memberIndex << " is protected" << std::endl;
}
int main()
{
constexpr RareTs::AccessMod accessModMemberZero = RareTs::access_modifier_v<MyObj, 0>;
constexpr RareTs::AccessMod accessModMemberOne = RareTs::access_modifier_v<MyObj, 1>;
constexpr RareTs::AccessMod accessModMemberTwo = RareTs::access_modifier_v<MyObj, 2>;
constexpr RareTs::AccessMod accessModMemberThree = RareTs::access_modifier_v<MyObj, 3>;
constexpr RareTs::AccessMod accessModMemberFour = RareTs::access_modifier_v<MyObj, 4>;
printNonPrivateMods(0, accessModMemberZero);
printNonPrivateMods(1, accessModMemberOne);
printNonPrivateMods(2, accessModMemberTwo);
printNonPrivateMods(3, accessModMemberThree);
printNonPrivateMods(4, accessModMemberFour);
}
class MyObj
{
int myInt = 3;
long myLong = 4;
char myChar = 'A';
public:
REFLECT(MyObj, myInt, myChar)
};
int main()
{
std::cout << "myInt was the " << RareTs::IndexOf<MyObj>::myInt << "th member reflected" << std::endl;
std::cout << "myChar was the " << RareTs::IndexOf<MyObj>::myChar << "st member reflected" << std::endl;
using MyIntMember = typename RareTs::MemberType<MyObj>::myInt;
using MyCharMember = typename RareTs::MemberType<MyObj>::myChar;
MyObj myObj{};
std::cout << MyIntMember::name << " had a default value of " << MyIntMember::value(myObj) << std::endl;
std::cout << MyCharMember::name << " had a default value of " << MyCharMember::value(myObj) << std::endl;
}
- intro to REFLECT macro
- intro to RareTs::Members
- detail document on REFLECT macro
- detail document on Members
class MyObj
{
bool a = true;
bool b = true;
bool c = false;
public:
REFLECT(MyObj, a, b, c)
};
int main()
{
MyObj myObj{};
RareTs::Values<MyObj>::pack(myObj, [&](auto & ... value) {
bool andResult = true && (... && value);
bool orResult = (... || value);
(std::cout << ... << (value ? " true &&" : " false &&")) << " = " << (andResult ? "true" : "false") << std::endl;
(std::cout << ... << (value ? " true ||" : " false ||")) << " = " << (orResult ? "true" : "false") << std::endl;
});
}
- intro to REFLECT macro
- intro to RareTs::Members
- detail document on REFLECT macro
- detail document on Members
class MyObj
{
int myInt = 0;
float myArray[3];
int & myRef;
public:
MyObj(int & referrable) : myInt(0), myArray{0.0f, 1.1f, 2.2f}, myRef(referrable) {}
REFLECT(MyObj, myInt, myArray, myRef)
};
int main()
{
int referrable = 1;
MyObj myObj(referrable);
RareTs::Members<MyObj>::forEach(myObj, [&](auto member, auto & value) { // Member has no non-static data members, so it's a bit better to pass it by value
using Member = decltype(member); // If you passed member by reference, you'd need: using Member = std::remove_reference_t<decltype(member)>;
using MemberType = typename Member::type; // Good, this is the type something was declared as in the structure
using ValueType = decltype(value); // Uusually bad, this decays, this aquires the reference/constness (or lack-thereof) of the lambda "value" param
std::cout << "[" << member.index << "]" << " had a declared type of " << RareTs::toStr<MemberType>() << std::endl
<< " and was passed to the function as: " << RareTs::toStr<ValueType>() << std::endl;
if constexpr ( std::is_array_v<MemberType> )
{
auto length = std::extent_v<MemberType>;
std::cout << " value: ";
for ( size_t i=0; i<length; ++i )
std::cout << value[i] << ", ";
std::cout << std::endl;
}
else
{
std::cout << " value: " << value << std::endl;
}
});
}
- intro to REFLECT macro
- intro to RareTs::Members
- detail document on REFLECT macro
- detail document on Members
class MyFilterableObj
{
int myInt = 0;
float myArray[3];
inline static int myStaticArray[3] { 2, 1, 0 };
int & myRef;
public:
MyFilterableObj(int & referrable) : myInt(0), myArray{2.2f, 1.1f, 0.0f}, myRef(referrable) {}
REFLECT(MyFilterableObj, myInt, myArray, myRef)
};
// This is a type-predicate, when you use type-predicates they only operate on Member::type, they can't access other details of Member
template <typename T> struct is_integral_or_integral_ref { static constexpr bool value = std::is_integral_v<RareTs::remove_cvref_t<T>>; };
// This is a member-predicate (the enable_if_member_t part is mandatory for member predicates), they can operator on the full details of Member
template <typename Member, typename = RareTs::enable_if_member_t<Member>>
struct IsNonStaticArray : std::bool_constant<!Member::isStatic && std::is_array_v<typename Member::type>> {};
int main()
{
int referrable = 1;
MyFilterableObj myObj(referrable);
RareTs::Members<MyFilterableObj>::forEach<std::is_integral>(myObj, [&](auto member, auto & value) { // Only matches int, will not match array or reference
std::cout << "is_integral: " << member.name << " = " << value << std::endl;
});
RareTs::Members<MyFilterableObj>::forEach<is_integral_or_integral_ref>(myObj, [&](auto member, auto & value) { // Matches both ints
std::cout << "is_integral_or_integral_ref: " << member.name << " = " << value << std::endl;
});
RareTs::Members<MyFilterableObj>::forEach<IsNonStaticArray>(myObj, [&](auto member, auto & value) { // Matches the non-static array only
std::cout << "IsNonStaticArray: " << member.name << " = " << value[0] << ", " << value[1] << ", " << value[2] << std::endl;
});
// If you access members using runtime information/index and perform an operation only valid on one of the members (e.g. array access), you must filter!
RareTs::Values<MyFilterableObj>::at(1, [&](auto & /*value*/) {
//std::cout << value[0] << std::endl; // This is a compile-time error since code for all members is generated
});
RareTs::Values<MyFilterableObj>::at<IsNonStaticArray>(1, myObj, [&](auto & value) {
std::cout << "predicate filtered: " << value[0] << std::endl; // This is fine since you've filtered to your particular types using IsNonStaticArray
});
RareTs::Members<MyFilterableObj>::at(1, myObj, [&](auto member, auto & value) { // This is more work, but ultimately a little more flexible at times
using Member = decltype(member);
if constexpr ( std::is_array_v<typename Member::type> )
std::cout << "if constexpr filtered: " << value[0] << std::endl; // This is fine since you've filtered to your particular types using if constexpr
});
}
- intro to REFLECT macro
- intro to RareTs::Members
- detail document on REFLECT macro
- detail document on Members
struct UnownedObject
{
int x = 1;
int y = 2;
};
class AnotherUnowned
{
protected:
int a = 3;
int b = 4;
};
template <> struct RareTs::Proxy<UnownedObject> : UnownedObject // This must be done at global scope
{
// Using the REFLECT macro in a proxy allows you to REFLECT information same as though you had placed it in the target classes definition
// This is especially useful for objects you do not own, e.g. those from other libraries
REFLECT(RareTs::Proxy<UnownedObject>, x, y)
};
template <> struct RareTs::Proxy<AnotherUnowned> : AnotherUnowned // You can reflect protected members since proxies inherit from the target
{
NOTE(a, "You can also place NOTE on members while proxying")
REFLECT(RareTs::Proxy<AnotherUnowned>, a, b)
};
int main()
{
UnownedObject point {};
std::cout << "UnownedObject: " << std::endl;
RareTs::Members<UnownedObject>::forEach(point, [&](auto member, auto & value) {
std::cout << " " << member.name << " = " << value << std::endl;
});
AnotherUnowned another {};
std::cout << "AnotherUnowned: " << std::endl;
RareTs::Members<AnotherUnowned>::forEach(another, [&](auto member, auto & value) {
std::cout << " " << member.name << " = " << value;
if constexpr ( member.template hasNote<const char*>() )
std::cout << " // " << member.template getNote<const char*>();
std::cout << std::endl;
});
}
class MyObj
{
const char a[5] = "asdf";
float b = 39.1f;
static constexpr int c = 42;
};
// REFLECT_PRIVATE is for reflecting objects you don't own which have private members, this is a common situation for objects from included libraries
// The REFLECT_PRIVATE macro must be used in the global scope and while syntatically identical to the in-class REFLECT macro it has additional limitations
// Namely it must be possible to create a pointer to the member, ergo reference members and overloaded members cannot be reflected and you can't get offsets
// REFLECT_PRIVATE does not involve casting/UB, it uses the standard-legal private-member exfoliation trick
REFLECT_PRIVATE(MyObj, a, b, c)
int main()
{
MyObj myObj {};
// Can be quite useful for whitebox-testing
static_assert(RareTs::whitebox(myObj).c == 42);
RareTs::whitebox(myObj).b = 133.7f;
// Or just regular serialization-like purposes
RareTs::Members<MyObj>::forEach(myObj, [&](auto member, auto & value) {
std::cout << RareTs::toStr<typename decltype(member)::type>() << " " << member.name << " = " << value << ";" << std::endl;
});
}
struct SubObj
{
int number = 0;
std::string str {"asdf"};
};
struct MyObj
{
SubObj subObj {};
int ray[5] {5, 6, 7, 8, 9};
std::string str {"qwerty"};
};
// As of C++20, member names (in addition to member types & values) can be reflected on all major compilers without any form of registration
// This cannot reflect classes using inheritance, private/protected members, virtualization, more than 121 members, or large C-arrays
// In addition, while not preventing auto-reflection of aggregates, static data members and functions are not auto-reflected
int main()
{
std::cout << Json::pretty(MyObj{}) << std::endl;
RareTs::Members<MyObj>::forEach(MyObj{}, [&](auto member) {
std::cout << member.index << ": " << member.name << std::endl;
});
}
struct MyObj
{
const char a[5] = "asdf";
float b = 39.1f;
static constexpr int c = 42;
};
// Very similar to REFLECT_PRIVATE except with a slightly different syntax allowing notes to be added to the reflected type or members
// Like REFLECT_PRIVATE, this must be done in the global scope and is purposed for types with private members that you can't place the REFLECT macro in
REFLECT_PRIVATE_NOTED(
(MyObj) (RareTs::Buildable),
(a) (Json::Ignore),
(b) (Json::Name{"testName"}, Json::Stringify),
(c) ()
)
int main()
{
MyObj myObj {};
RareTs::Members<MyObj>::forEach(myObj, [&](auto member, auto & value) {
std::cout << RareTs::toStr<typename decltype(member)::type>() << " " << member.name << " = " << value << ";" << std::endl;
});
std::cout << Json::pretty(myObj);
}
struct RegularMembers
{
int a = 1;
int b = 2;
int c = 3;
REFLECT(RegularMembers, a, b, c)
};
// You can use notes (aka annotations) to tag fields with additional compile-time info, such as renaming or ignoring a field in serializers
// NOTE has the form NOTE(memberName, noteValues), where noteValues can include any constexpr-friendly value/object
// By convention NOTE comes just before the thing it's NOTE'ing, but it's only required to come before the REFLECT macro in which the thing is reflected
struct NotedMembers
{
NOTE(a, Json::Name{"int"}, Json::Stringify, 0, "asdf")
int a = 1;
int b = 2;
NOTE(c, Json::Ignore)
int c = 3;
REFLECT(NotedMembers, a, b, c)
};
int main()
{
RegularMembers regularMembers{};
NotedMembers notedMembers{};
std::cout << "regularMembers: " << Json::pretty(regularMembers) << std::endl;
std::cout << "notedMembers: " << Json::pretty(notedMembers) << std::endl;
std::cout << "a has int note? : " << (RareTs::MemberType<NotedMembers>::a::hasNote<int>() ? "true" : "false") << std::endl;
std::cout << "a has float note? : " << (RareTs::MemberType<NotedMembers>::a::hasNote<float>() ? "true" : "false") << std::endl;
std::cout << "a has a const char* note with value: " << RareTs::MemberType<NotedMembers>::a::getNote<const char*>() << std::endl;
std::cout << "all a note types: ";
RareTs::MemberType<NotedMembers>::a::forEachNote([&](auto & note) {
std::cout << RareTs::toStr<RareTs::remove_cvref_t<decltype(note)>>() << ", ";
});
std::cout << std::endl;
}
struct Super {};
struct RegularClass : Super
{
REFLECT(RegularClass)
};
NOTE(NotedClass, RareTs::Super<Super>) // A class-level annotation can attach info to the class type, e.g. declaring supers
struct NotedClass : Super
{
REFLECT_NOTED(NotedClass) // Note, you need a different macro for *CLASS* level annotations to work, member-level annotations work with either macro
};
// Class-level annotations have essentially the same capabilities/limits/interface as member-level annotations
int main()
{
std::cout << "RegularClass has " << RareTs::Supers<RegularClass>::total << " known super-classes" << std::endl;
std::cout << "NotedClass has " << RareTs::Supers<NotedClass>::total << " known super-classes" << std::endl;
using RegularClassSuper = RareTs::Supers<RegularClass>::SuperType<0>;
using NotedClassSuper = RareTs::Supers<NotedClass>::SuperType<0>;
std::cout << "RegularClass super: " << RareTs::toStr<RegularClassSuper>() << std::endl;
std::cout << "NotedClass super: " << RareTs::toStr<NotedClassSuper>() << std::endl;
std::cout << "all NotedClass note types: ";
RareTs::Notes<NotedClass>::forEachNote([&](auto & note) {
using Note = RareTs::remove_cvref_t<decltype(note)>;
std::cout << RareTs::toStr<Note>() << ", ";
});
std::cout << std::endl;
}
struct RegularClass
{
int a = 0;
int b = 0;
REFLECT(RegularClass, a, b)
};
struct OtherClass
{
int eyy = 0;
int c = 0;
int bee = 0;
REFLECT(OtherClass, eyy, c, bee)
// Rarely you might use an "anonymous" note, which does NOT refer to an identifier used in the REFLECT macro
// These can be useful when a reflection extension needs to refer to the REFLECT metadata of the class in which it resides, as is the case with RareMapper
NOTE(ObjectMappings, RareMapper::createMapping<RegularClass, OtherClass>()
.a->eyy()
.b->bee()
.bidirectional())
};
int main()
{
RegularClass reg {3, 4};
std::cout << Json::pretty(reg) << std::endl;
OtherClass other = RareMapper::map<OtherClass>(reg); // Finds & uses the anonymous note "OtherClass::ObjectMappings" to figure out which fields to assign
std::cout << Json::pretty(other) << std::endl;
}
using Alias = std::string_view;
struct NotedMembers
{
NOTE(a, 0, "asdf", 1, 2, Alias{"ayy"}, "qwerty")
int a = 1;
int b = 2;
int c = 3;
REFLECT(NotedMembers, a, b, c)
};
int main()
{
// The note interface can use predicate-based filtering on the type of the values given in the NOTE
std::cout << "first const char*: " << RareTs::MemberType<NotedMembers>::a::getNote<const char*>() << std::endl;
std::cout << "first int: " << RareTs::MemberType<NotedMembers>::a::getNote<int>() << std::endl;
std::cout << "first alias: " << RareTs::MemberType<NotedMembers>::a::getNote<Alias>() << std::endl;
std::cout << "all const char*'s: ";
RareTs::MemberType<NotedMembers>::a::forEachNote<const char*>([&](auto & note) {
std::cout << note << ", ";
});
std::cout << std::endl;
std::cout << "all ints: ";
RareTs::MemberType<NotedMembers>::a::forEachNote<int>([&](auto & note) {
std::cout << note << ", ";
});
std::cout << std::endl;
std::cout << "all aliases: ";
RareTs::MemberType<NotedMembers>::a::forEachNote<Alias>([&](auto & note) {
std::cout << note << ", ";
});
std::cout << std::endl;
}
struct ParentA { int a=0; REFLECT(ParentA, a) };
struct ParentB { int b=1; REFLECT(ParentB, b) };
NOTE(ChildA, RareTs::Super<ParentA>) // Supers are defined in a class-level annotations like so
struct ChildA : ParentA
{
REFLECT_NOTED(ChildA) // Class-level annotations require the "REFLECT_NOTED" rather than the "REFLECT" macro (not required for other note categories)
};
NOTE(ChildB,
RareTs::Super<ParentA>(Json::Name{"parA"}), // Super-class relationships can in turn have their own notes set in the optional parens after a super-declaration
RareTs::Super<ParentB>(Json::Ignore))
struct ChildB : ParentA, ParentB
{
REFLECT_NOTED(ChildB) // Class-level annotations require the "REFLECT_NOTED" rather than the "REFLECT" macro (not required for other note categories)
};
int main()
{
std::cout << "ChildA: " << std::endl;
RareTs::Supers<ChildA>::forEach([&](auto superInfo) {
using SuperInfo = decltype(superInfo);
constexpr size_t index = SuperInfo::index;
using type = typename SuperInfo::type;
std::cout << "[" << index << "] " << RareTs::toStr<type>() << std::endl;
});
ChildB childB {};
std::cout << std::endl << "ChildB: " << std::endl;
RareTs::Supers<ChildB>::forEach([&](auto superInfo) {
using SuperInfo = decltype(superInfo);
constexpr size_t index = SuperInfo::index;
using type = typename SuperInfo::type;
constexpr bool ignored = SuperInfo::template hasNote<Json::IgnoreType>();
std::cout << "[" << index << "] " << RareTs::toStr<type>() << (ignored ? " (Json Ignored)" : "") << std::endl;
});
std::cout << Json::pretty(childB) << std::endl;
RareTs::Supers<ChildB>::forEach(childB, [&](auto superInfo, auto & super) { // Second param is childB casted to the current iterations super-type
using SuperInfo = decltype(superInfo);
using type = typename SuperInfo::type;
std::cout << "(" << RareTs::toStr<type>() << ")childB: " << Json::pretty(super) << std::endl;
});
}
- intro to Supers
- detail document on Supers
- detail document on REFLECT macro
- detail document on Super annotation
struct ParentA { int a=0; REFLECT(ParentA, a) };
struct ParentB { int b=1; REFLECT(ParentB, b) };
NOTE(Child,
RareTs::Super<ParentA>,
RareTs::Super<ParentB>)
struct Child : ParentA, ParentB
{
REFLECT_NOTED(Child)
};
int main()
{
using zeroth = RareTs::Supers<Child>::SuperInfo<0>::type;
using first = RareTs::Supers<Child>::SuperInfo<1>::type;
std::cout << "[0] " << RareTs::toStr<zeroth>() << std::endl;
std::cout << "[1] " << RareTs::toStr<first>() << std::endl;
}
- intro to Supers
- detail document on Supers
- detail document on REFLECT macro
- detail document on Super annotation
struct ParentA { int a=0; REFLECT(ParentA, a) };
struct ParentB { int b=1; REFLECT(ParentB, b) };
NOTE(Child,
RareTs::Super<ParentA>,
RareTs::Super<ParentB>)
struct Child : ParentA, ParentB
{
REFLECT_NOTED(Child)
};
int main()
{
size_t superIndex = 0;
std::cout << "Enter a super index: ";
std::cin >> superIndex;
RareTs::Supers<Child>::at(superIndex, [&](auto superInfo) {
using SuperInfo = decltype(superInfo);
std::cout << "[" << SuperInfo::index << "] " << RareTs::toStr<typename SuperInfo::type>() << std::endl;
});
}
- intro to Supers
- detail document on Supers
- detail document on REFLECT macro
- detail document on Super annotation
struct GrandparentA {};
struct GrandparentB {};
NOTE(ParentA, RareTs::Super<GrandparentA>)
struct ParentA : GrandparentA { REFLECT_NOTED(ParentA) };
NOTE(ParentB, RareTs::Super<GrandparentB>)
struct ParentB : GrandparentB { REFLECT_NOTED(ParentB) };
NOTE(Child, RareTs::Super<ParentA>, RareTs::Super<ParentB>)
struct Child : ParentA, ParentB { REFLECT_NOTED(Child) };
template <typename T, size_t Level = 0>
void printTree()
{
if constexpr ( Level > 0 )
{
for ( size_t i=0; i<Level; ++i )
std::cout << " ";
}
std::cout << RareTs::toStr<T>() << std::endl;
if constexpr ( RareTs::is_reflected_v<T> )
{
RareTs::Supers<T>::forEach([](auto superInfo) { // Note that this only loops over the (zero, one, or multiple) inherited classes immediately on type T
printTree<typename decltype(superInfo)::type, Level+1>(); // Generally need to grab the type of a super and recurse on it to get to ancestors
});
}
}
int main()
{
printTree<Child>();
}
- intro to Supers
- detail document on Supers
- detail document on REFLECT macro
- detail document on Super annotation
struct MyController
{
void foo() {
std::cout << "foo" << std::endl;
}
int bar(int value) {
std::cout << "bar(" << value << ") = ";
return value+1;
}
static void staticFoo() {
std::cout << "staticFoo" << std::endl;
}
REFLECT(MyController, foo, bar, staticFoo)
};
int main()
{
MyController myObj {};
RareTs::Members<MyController>::forEach(myObj, [&](auto member, auto & value) {
using PointerType = typename decltype(member)::pointer_type;
constexpr size_t totalArgs = RareTs::argument_count_v<PointerType>;
using ReturnType = RareTs::return_type_t<PointerType>; // The return type of the function
using MemberOf = RareTs::member_of_t<PointerType>; // If this is a non-static member function, this gets the type it's a non-static member function in
std::cout << "[" << member.index << "] " << RareTs::toStr<PointerType>() << std::endl; // Print function pointer (~the unnamed function signature)
// Print ~the same signature except using the granular types we got from reflection/type introspection
std::cout << "[" << member.index << "] " << RareTs::toStr<ReturnType>() << " ";
if constexpr ( !std::is_void_v<MemberOf> )
std::cout << RareTs::toStr<MemberOf>() << "::";
std::cout << member.name << "(";
RareTs::forIndexes<totalArgs>([&](auto i) {
constexpr size_t tupleIndex = decltype(i)::value;
using ArgumentType = RareTs::argument_t<tupleIndex, PointerType>;
std::cout << RareTs::toStr<ArgumentType>();
});
std::cout << ")" << std::endl;
using ArgumentTypes = RareTs::arguments_t<PointerType>; // Get arguments as tuple
// Call the function, it's a little different for member functions vs static functions, and functions may differ by their arguments
std::cout << " ";
if constexpr ( std::is_same_v<std::tuple<int>, ArgumentTypes> ) // is a function taking an int argument
std::cout << (myObj.*value)(1) << std::endl;
else if constexpr ( std::is_same_v<std::tuple<>, ArgumentTypes> && !member.isStatic ) // is a no-argument function
(myObj.*value)();
else if constexpr ( member.isStatic )
value();
});
}
- intro to REFLECT macro
- intro to RareTs::Members
- detail document for function introspection
- detail document on Member
struct MyController
{
std::string foo(int a, int b, float c) {
return std::string("I got ") + std::to_string(a) + "," + std::to_string(b) + "," + std::to_string(c);
}
int bar(int a, int b) {
return a+b;
}
REFLECT(MyController, foo, bar)
};
template <typename T>
void invokeControllerMethod(T & controller, std::string_view name, std::string_view input)
{
std::istringstream ss(std::string(input).c_str());
auto argBuilder = [&](auto argInfo) {
using type = typename decltype(argInfo)::type;
//constexpr size_t index = decltype(argInfo)::index;
type arg {};
ss >> arg;
return arg;
};
RareTs::Members<T>::named(name, [&](auto member) {
auto func = RareTs::Function(member.pointer);
auto result = func.invoke(controller, argBuilder);
std::cout << "result of call to " << name << ": " << result << std::endl;
});
}
int main()
{
MyController myController {};
invokeControllerMethod(myController, "foo", "0 1 3.4");
invokeControllerMethod(myController, "bar", "2 3");
}
- intro to REFLECT macro
- intro to RareTs::Members
- detail document for function introspection
- detail document on Member
struct MyObj
{
NOTE(foo, RareTs::Overload<int, float>, RareTs::Overload<std::string_view>)
void foo(int i, float f) {
std::cout << " called foo(int, float) with: " << i << ", " << f << std::endl;
}
void foo(std::string_view s) {
std::cout << " called foo(std::string_view) with: " << s << std::endl;
}
REFLECT(MyObj, foo)
};
int main()
{
MyObj myObj{};
RareTs::MemberType<MyObj>::foo::Overloads::forEach(myObj, [&](auto overloadInfo) {
using OverloadInfo = decltype(overloadInfo);
using ArgumentTypes = typename OverloadInfo::Arguments;
if constexpr ( std::is_same_v<std::tuple<int, float>, ArgumentTypes> )
(myObj.*(overloadInfo.pointer))(0, 1.1f);
else
(myObj.*(overloadInfo.pointer))("qwerty");
});
}
struct MyObj
{
NOTE(foo, RareTs::Overload<int, float>, RareTs::Overload<std::string_view>)
int foo(int i, float f) {
std::cout << " called foo(int, float) with: " << i << ", " << f << std::endl;
return i + (int)f;
}
std::string_view foo(std::string_view s) {
std::cout << " called foo(std::string_view) with: " << s << std::endl;
s.remove_prefix(1);
return s;
}
REFLECT(MyObj, foo)
};
int main()
{
using ZerothFooOverload = RareTs::MemberType<MyObj>::foo::Overloads::Overload<0>;
using FirstFooOverload = RareTs::MemberType<MyObj>::foo::Overloads::Overload<1>;
using ZerothArgSet = ZerothFooOverload::Arguments;
using FirstArgSet = FirstFooOverload::Arguments;
using ZerothReturnType = ZerothFooOverload::Return;
using FirstReturnType = FirstFooOverload::Return;
std::cout << RareTs::toStr<ZerothArgSet>() << std::endl;
std::cout << " returns " << RareTs::toStr<ZerothReturnType>() << std::endl;
std::cout << RareTs::toStr<FirstArgSet>() << std::endl;
std::cout << " returns " << RareTs::toStr<FirstReturnType>() << std::endl;
}
struct MyObj
{
NOTE(foo, RareTs::Overload<int, float>, RareTs::Overload<std::string_view>)
void foo(int i, float f) {
std::cout << " called foo(int, float) with: " << i << ", " << f << std::endl;
}
void foo(std::string_view s) {
std::cout << " called foo(std::string_view) with: " << s << std::endl;
}
REFLECT(MyObj, foo)
};
int main()
{
std::size_t i = 0;
std::cout << "Enter the index of an overload: ";
std::cin >> i;
MyObj myObj{};
RareTs::MemberType<MyObj>::foo::Overloads::at(myObj, i, [&](auto overloadInfo) {
using OverloadInfo = decltype(overloadInfo);
using ArgumentTypes = typename OverloadInfo::Arguments;
if constexpr ( std::is_same_v<std::tuple<int, float>, ArgumentTypes> )
(myObj.*(overloadInfo.pointer))(0, 1.1f);
else
(myObj.*(overloadInfo.pointer))("qwerty");
});
}
struct MyObj
{
NOTE(foo, RareTs::Overload<int, float>, RareTs::Overload<std::string_view>)
void foo(int i, float f) {
std::cout << " called foo(int, float) with: " << i << ", " << f << std::endl;
}
void foo(std::string_view s) {
std::cout << " called foo(std::string_view) with: " << s << std::endl;
}
REFLECT(MyObj, foo)
};
template <typename T>
void useOverload(T & overload)
{
std::cout << RareTs::toStr<typename RareTs::remove_cvref_t<decltype(overload)>::Arguments>() << std::endl;
}
int main()
{
RareTs::MemberType<MyObj>::foo::Overloads::pack(MyObj{}, [&](auto ... overloadInfo) {
(useOverload(overloadInfo), ...);
});
}
struct MyObj
{
void foo(int i, float f) {
std::cout << " called foo(int, float) with: " << i << ", " << f << std::endl;
}
void foo(std::string_view s) {
std::cout << " called foo(std::string_view) with: " << s << std::endl;
}
REFLECT(MyObj, foo)
};
int main()
{
// If the arguments weren't declared in the NOTEs you can still get overloads if you know the EXACT type of the argument (implicit conversion not allowed)
using FooIntFloatOverload = RareTs::OverloadInfo<MyObj, RareTs::IndexOf<MyObj>::foo, int, float>;
using FooStringViewOverload = RareTs::OverloadInfo<MyObj, RareTs::IndexOf<MyObj>::foo, std::string_view>;
std::cout << RareTs::toStr<typename FooIntFloatOverload::pointer_type<>>() << std::endl;
std::cout << RareTs::toStr<typename FooStringViewOverload::pointer_type<>>() << std::endl;
MyObj myObj {};
(myObj.*FooIntFloatOverload::pointer<>)(0, 1.1f);
(myObj.*FooStringViewOverload::pointer<>)("asdf");
}
struct MyObj
{
NOTE(foo, RareTs::Overload<int, float>, RareTs::Overload<std::string_view>)
void foo(int i, float f) {
std::cout << " called foo(int, float) <<NON CONST>> with: " << i << ", " << f << std::endl;
}
void foo(int i, float f) const {
std::cout << " called foo(int, float) <<CONST>> with: " << i << ", " << f << std::endl;
}
void foo(std::string_view s) {
std::cout << " called foo(std::string_view) with: " << s << std::endl;
}
REFLECT(MyObj, foo)
};
int main()
{
const MyObj myObj{};
std::cout << "Least qualified: " << std::endl;
RareTs::MemberType<MyObj>::foo::Overloads::forEach([&](auto overloadInfo) { // Here you haven't supplied anything indicating the cvref qualification
using OverloadInfo = decltype(overloadInfo); // Consequently you're only get the "least" qualified version of overloads
using Pointer = typename OverloadInfo::pointer_type;
std::cout << " " << RareTs::toStr<Pointer>() << std::endl;
});
std::cout << "const qualified: " << std::endl;
RareTs::MemberType<MyObj>::foo::Overloads::forEach<const MyObj>([&](auto overloadInfo) { // Here you've specified a qualification using the template param
using OverloadInfo = decltype(overloadInfo); // Consequently you're only get the const version of overloads
using Pointer = typename OverloadInfo::pointer_type;
std::cout << " " << RareTs::toStr<Pointer>() << std::endl;
(myObj.*overloadInfo.pointer)(0, 2.2f);
});
std::cout << "unqualified: " << std::endl;
RareTs::MemberType<MyObj>::foo::Overloads::forEach<MyObj>([&](auto overloadInfo) { // Here you've specified a qualification using the template param
using OverloadInfo = decltype(overloadInfo); // Consequently you're only get the given qualification (const) versions of overloads
using Pointer = typename OverloadInfo::pointer_type;
using ArgumentTypes = typename OverloadInfo::Arguments;
std::cout << " " << RareTs::toStr<Pointer>() << std::endl;
std::cout << " " << RareTs::toStr<ArgumentTypes>() << std::endl;
});
std::cout << "resolved on const instance: " << std::endl;
RareTs::MemberType<MyObj>::foo::Overloads::forEach(myObj, [&](auto overloadInfo) { // Here you supply an instance of MyObj
using OverloadInfo = decltype(overloadInfo); // Consequently you're getting the best-match overload for how your instance of MyObj was qualified
using Pointer = typename OverloadInfo::pointer_type;
std::cout << " " << RareTs::toStr<Pointer>() << std::endl;
(myObj.*overloadInfo.pointer)(0, 2.2f);
});
}
struct structure {};
template <typename T> struct templated {};
int main()
{
std::cout << "int: " << RareTs::toStr<int>() << std::endl;
std::cout << "structure: " << RareTs::toStr<structure>() << std::endl;
std::cout << "templated<float>: " << RareTs::toStr<templated<float>>() << std::endl;
}
int main()
{
using TypeList = std::tuple<int, char, float>;
constexpr size_t totalTypes = std::tuple_size_v<TypeList>;
size_t index = 0;
std::cout << "Enter a tuple index (between 0 and 2): ";
std::cin >> index;
// For some small maximum value (e.g. totalTypes; this can't be too large)
// you can turn a runtime index ("index") into a std::integral_constant ("i")
// from which you can take a constexpr index ("decltype(i)::value")
// which can be used as a template parameter/tuple index and such
RareTs::forIndex<totalTypes>(index, [&](auto i) {
constexpr size_t constexprIndex = decltype(i)::value;
using SelectedTupleElement = std::tuple_element_t<constexprIndex, TypeList>;
std::cout << "Selected tuple element: " << RareTs::toStr<SelectedTupleElement>() << std::endl;
});
// You can also loop over a set of constexpr indexes for some given maximum in a similar fashion
RareTs::forIndexes<totalTypes>([&](auto i) {
constexpr size_t constexprIndex = decltype(i)::value;
using SelectedTupleElement = std::tuple_element_t<constexprIndex, TypeList>;
std::cout << constexprIndex << ":" << RareTs::toStr<SelectedTupleElement>() << "," << std::endl;
});
}
class Blackbox
{
private:
int a = 3;
int b = 4;
int c = 5;
public:
REFLECT(Blackbox, a, b, c)
int checksum() { return a+b+c; }
};
int main()
{
Blackbox blackbox {};
std::cout << blackbox.checksum() << std::endl;
auto whitebox = RareTs::whitebox(blackbox); // whitebox is a object with public members that are reference to the given objects members
std::cout << "a: " << whitebox.a << std::endl;
std::cout << "b: " << whitebox.b << std::endl;
std::cout << "c: " << whitebox.c << std::endl;
// You can use it to edit the members of blackbox
whitebox.a++;
whitebox.b = 5;
whitebox.c++;
std::cout << blackbox.checksum() << std::endl;
// Highly recommeneded that you only use this for testing purposes, working around access protection is generally a bad idea
// General caution is recommended when any reflection that you're not accidentally working around protection and invalidating object invariants
}
struct MyObj
{
int a;
int b;
std::string c;
REFLECT(MyObj, a, b, c)
};
template <typename T>
auto toTuple(T & t)
{
return RareTs::Values<MyObj>::pack(t, [&](auto & ... value) {
return std::tie(value...);
});
}
int main()
{
MyObj myObj {1, 2, "3"}; // I don't generally recommend forming tuples out of structures, generally it's unnecessary indirection/unhelpful
auto tup = toTuple(myObj); // But if you get something out of doing it, go for it
std::cout << RareTs::toStr<decltype(tup)>() << std::endl;
std::cout << std::get<0>(tup) << std::endl;
std::cout << std::get<1>(tup) << std::endl;
std::cout << std::get<2>(tup) << std::endl;
}
struct MyObj
{
int a;
int b;
std::string c;
REFLECT(MyObj, a, b, c)
};
int main()
{
MyObj myObj {1, 2, "3"}; // This can be turned into something that's tuple-like without the type-lossy nature of the std::tie approach
auto tup = RareTs::tuplify(myObj); // "tup" wraps a reference to myObj, can be used with tuple-like interfaces & lets you use structured bindings
std::cout << "tuple size: " << std::tuple_size_v<decltype(tup)> << std::endl;
std::cout << RareTs::toStr<std::tuple_element_t<0, decltype(tup)>>() << ": " << std::get<0>(tup) << std::endl;
std::cout << RareTs::toStr<std::tuple_element_t<1, decltype(tup)>>() << ": " << std::get<1>(tup) << std::endl;
std::cout << RareTs::toStr<std::tuple_element_t<2, decltype(tup)>>() << ": " << std::get<2>(tup) << std::endl;
auto & [a, b, c] = tup; // structured binding
std::cout << a << ", " << b << ", " << c << std::endl;
using Tup = RareTs::member_type_tuple_t<MyObj>; // Can also get a std::tuple type representative of the members if only metaprogramming is needed
std::cout << RareTs::toStr<Tup>() << std::endl;
}
struct MyObj
{
int a;
int b;
std::string c;
REFLECT(MyObj, a, b, c)
template <size_t I> friend constexpr auto & get(MyObj & obj) { return RareTs::Member<MyObj, I>::value(obj); } // Need a get function(s)
template <size_t I> friend constexpr auto & get(const MyObj & obj) { return RareTs::Member<MyObj, I>::value(obj); }
// also sometimes MyObj && and const MyObj &&
};
template <size_t I, typename T>
constexpr decltype(auto) get(T &&) = delete;
// Adding tuple protocol directly to your types involves adding some specializations/overloads to std, the in-object REFLECT macro simply can't do this
// Another macro in the global scope might be able to do this for you some of the time, but not covering all cases (namely templated classes)
// Consequently there's some manual work to enable direct tuplification, though RareCpp does save you from needing per-member specializations/overloads
namespace std
{
template <> struct tuple_size<MyObj> { static constexpr size_t value = RareTs::Members<MyObj>::total; }; // Need a std::tuple_size specialization
template <size_t I> struct tuple_element<I, MyObj> { using type = typename RareTs::Member<MyObj, I>::type; }; // Need a std::tuple_element specialization
}
int main()
{
MyObj myObj {1, 2, "3"};
std::cout << "tuple size: " << std::tuple_size_v<MyObj> << std::endl;
std::cout << RareTs::toStr<std::tuple_element_t<0, MyObj>>() << ": " << get<0>(myObj) << std::endl;
std::cout << RareTs::toStr<std::tuple_element_t<1, MyObj>>() << ": " << get<1>(myObj) << std::endl;
std::cout << RareTs::toStr<std::tuple_element_t<2, MyObj>>() << ": " << get<2>(myObj) << std::endl;
auto & [a, b, c] = myObj; // structured binding
std::cout << a << ", " << b << ", " << c << std::endl;
}
inline namespace AdaptiveCloning
{
template <typename T, size_t I = std::numeric_limits<size_t>::max()>
struct partition_member : RareTs::Class::adapt_member<partition_member<T>::template type, T, I> {};
template <typename T>
struct partition_member<T, std::numeric_limits<size_t>::max()> {
template <size_t I> using type = RareTs::Class::member_type<T, I>;
};
template <typename T, size_t ... Is>
struct partition_members : RareTs::Class::adapt_member<partition_member<T>::template type, T, Is> ... {};
template <typename T, size_t ... Is>
constexpr partition_members<T, Is...> cloneMembers(std::index_sequence<Is...>);
template <typename T> using clone_members = decltype(cloneMembers<T>(std::make_index_sequence<RareTs::Class::member_count<T>>()));
}
struct MyObj
{
int a;
std::string b;
float c;
REFLECT(MyObj, a, b, c)
};
int main()
{
using OpaqueObj = clone_members<MyObj>;
OpaqueObj opaqueObj{};
static_assert(!std::is_same_v<MyObj, OpaqueObj>, "These are different types, but they have the same/cloned members");
opaqueObj.a = 1;
opaqueObj.b = 2;
opaqueObj.c = 3;
std::cout << "Made opaque clone {a, b, c}" << std::endl;
using PartitionObj = decltype(cloneMembers<MyObj>(std::index_sequence<0, 2>{}));
PartitionObj partitionObj{};
partitionObj.a = 1;
//partitionObj.b = 2; // the field "b" at index 1 was not cloned, only fields 0 and 2 were cloned
partitionObj.c = 3;
std::cout << "Made partition {a:" << partitionObj.a << ", c:" << partitionObj.c << "}" << std::endl;
}
inline namespace AdaptiveTypedefs
{
template <typename T, size_t I = std::numeric_limits<size_t>::max()>
struct partition_member_type : RareTs::Class::adapt_member_type<partition_member_type<T>::template type, T, I> {};
template <typename T>
struct partition_member_type<T, std::numeric_limits<size_t>::max()> {
template <size_t I> using type = RareTs::Class::member_type<T, I>;
};
template <typename T, size_t ... Is>
struct partition_member_types : RareTs::Class::adapt_member_type<partition_member_type<T>::template type, T, Is> ... {};
template <typename T, size_t ... Is>
constexpr partition_member_types<T, Is...> memberTypedefs(std::index_sequence<Is...>);
template <typename T> using member_typedefs = decltype(memberTypedefs<T>(std::make_index_sequence<RareTs::Class::member_count<T>>()));
}
struct MyObj
{
int a;
std::string b;
float c;
REFLECT(MyObj, a, b, c)
};
int main()
{
using MemberTypedefs = member_typedefs<MyObj>;
std::cout << "a: " << RareTs::toStr<MemberTypedefs::a>() << std::endl;
std::cout << "b: " << RareTs::toStr<MemberTypedefs::b>() << std::endl;
std::cout << "c: " << RareTs::toStr<MemberTypedefs::c>() << std::endl << std::endl;
using PartitionTypedefs = decltype(memberTypedefs<MyObj>(std::index_sequence<1, 2>{}));
//std::cout << RareTs::toStr<PartitionTypedefs::a>() << std::endl; // Member index 0 was not supplied for this partition, so field 'a' is not present
std::cout << "b: " << RareTs::toStr<PartitionTypedefs::b>() << std::endl;
std::cout << "c: " << RareTs::toStr<PartitionTypedefs::c>() << std::endl;
}
inline namespace AdaptiveWhiteboxer
{
template <typename T, size_t I> struct type_wrapper { using type = typename RareTs::Class::template member_type<T, I>; }; // Work-around gcc access bugs
template <typename T, size_t I = std::numeric_limits<size_t>::max()>
struct reference_member : RareTs::Class::adapt_member<reference_member<T>::template type, T, I> {};
template <typename T>
struct reference_member<T, std::numeric_limits<size_t>::max()> {
template <size_t I> using type = std::add_lvalue_reference_t<typename type_wrapper<T, I>::type>;
};
template <typename T, size_t ... Is>
struct reference_members_adapter : RareTs::Class::adapt_member<reference_member<T>::template type, T, Is>... // Adapter extends the Ith members type ref
{
constexpr reference_members_adapter(T & t) // Initializes the reference values
: RareTs::Class::adapt_member<reference_member<T>::template type, T, Is> {{ RareTs::Class::memberValue<Is>(t) }}... {}
};
template <typename T, size_t ... Is> constexpr auto whitebox(T & t, std::index_sequence<Is...>) {
return reference_members_adapter<T, Is...>(t);
}
template <typename T> constexpr auto whitebox(T & t) {
return RareTs::template Members<T>::template pack<RareTs::Filter::IsData>([&](auto & ... member) {
return whitebox(t, std::index_sequence<RareTs::remove_cvref_t<decltype(member)>::index...>{});
});
}
}
class MyObj
{
int a = 1;
int b = 2;
int c = 3;
public:
REFLECT(MyObj, a, b, c)
int checksum() { return a+b+c; }
};
int main()
{
MyObj myObj{};
auto ref = whitebox(myObj);
std::cout << myObj.checksum() << std::endl;
std::cout << "a: " << ref.a << std::endl;
std::cout << "b: " << ref.b << std::endl;
std::cout << "c: " << ref.c << std::endl;
ref.a++;
ref.b++;
ref.c++;
std::cout << myObj.checksum() << std::endl;
}
inline namespace AdaptiveBuilder
{
template <typename T, size_t ... Is> class sub_builder;
template <typename T, typename Builder, size_t ... Is> struct member_builder {
template <size_t I> class type {
using Member = typename RareTs::Reflect<T>::Members::template Member<I>;
template <size_t ... Js> constexpr auto remaining(std::index_sequence<Js...>) { return sub_builder<T, Js...>(t); }
T & t;
public:
constexpr type(T & t) : t(t) {}
constexpr auto operator() (const typename Member::type & value) {
Member::value(t) = value;
return remaining(typename RareTs::remove_index<I, Is...>::type{}); // Remove member index from set of indexes after you've used it
// Return a builder ("sub_builder") for remaining members
}
};
};
template <typename T, size_t ... Is>
class sub_builder : public RareTs::Class::adapt_member<member_builder<T, sub_builder<T, Is...>, Is...>::template type, T, Is>...
{
T & t;
public:
constexpr T build() { return t; }
constexpr sub_builder(T & t) : RareTs::Class::adapt_member<member_builder<T, sub_builder<T, Is...>, Is...>::template type, T, Is> {{t}}..., t(t) {}
};
template <typename T, size_t ... Is>
class head_builder : public sub_builder<T, Is...>
{
T t{};
public:
constexpr head_builder() : sub_builder<T, Is...>(t), t({}) {}
};
template <typename T, size_t ... Is> constexpr auto builder(std::index_sequence<Is...>) {
return head_builder<T, Is...>{};
}
template <typename T> constexpr auto builder() {
// Create a builder using member indexes, you could apply a member filter here (e.g. use non-static data members only)
return RareTs::template Members<T>::template pack([&](auto & ... member) {
return builder<T>(std::index_sequence<RareTs::remove_cvref_t<decltype(member)>::index...>{});
});
}
}
struct MyObj
{
int a;
std::string b;
float c;
REFLECT(MyObj, a, b, c)
};
int main()
{
// Builder for reflected members, strongly typed, removes members from list of those available to set after they're used
auto myObj = builder<MyObj>().a(1).c(2.2f).b("asdf").build();
std::cout << Json::pretty(myObj) << std::endl;
}
inline namespace AdaptiveChangeListener
{
template <typename T> struct member_change_listener {
template <size_t I> class type {
using Member = typename RareTs::Reflect<T>::Members::template Member<I>;
using MemberType = typename Member::type;
using Listener = std::function<void(const T &, const Member &, const MemberType & oldValue, const MemberType & newValue)>;
static constexpr auto member = Member{};
T & t;
std::list<Listener> listeners {};
public:
class UniqueListener {
std::list<Listener> & in;
typename std::list<Listener>::iterator it;
public:
UniqueListener(std::list<Listener> & in, typename std::list<Listener>::iterator it) : in(in), it(it) {}
UniqueListener(UniqueListener && other) noexcept : in (other.in), it(std::move(other.it)) { other.it = in.end(); }
~UniqueListener() { if ( it != in.end() ) in.erase(it); }
};
auto & operator=(MemberType newValue) {
auto & value = Member::value(t);
std::swap(value, newValue);
for ( const auto & listener : listeners )
listener(t, member, newValue, value);
return *this;
}
auto make_unique_listener(Listener && listener) {
listeners.push_front(listener);
return UniqueListener(listeners, listeners.begin());
}
type(T & t, typename member_change_listener<T>::template type<I>* & self) : t(t) { self = this; }
};
};
template <typename T, size_t ... Is>
class ChangeListenerAdapter : public RareTs::Class::adapt_member<member_change_listener<T>::template type, T, Is>...
{
std::tuple<typename member_change_listener<T>::template type<Is>*...> members;
public:
template <typename F> inline auto make_unique_listener(F && f) {
return std::tuple { std::move(std::get<Is>(members)->make_unique_listener(std::forward<F>(f)))... };
}
ChangeListenerAdapter(T & t, decltype(members) f = {})
: RareTs::Class::adapt_member<member_change_listener<T>::template type, T, Is>{{t, std::get<Is>(f)}}... { members.swap(f); }
};
template <typename T, size_t ... Is> inline auto changeListener(T & t, std::index_sequence<Is...>) {
return ChangeListenerAdapter<T, Is...>(t);
}
template <typename T> inline auto changeListener(T & t) {
return changeListener(t, std::make_index_sequence<RareTs::Class::member_count<T>>());
}
}
struct MyObj
{
int myInt = 0;
std::string myString = "init";
REFLECT(MyObj, myInt, myString)
};
int main()
{
MyObj myObj{};
std::cout << Json::pretty(myObj) << std::endl << std::endl;
auto myTappedObj = changeListener(myObj); // adapted types are given operator= and listen(...)
auto listener = myTappedObj.make_unique_listener([&](auto & t, auto & member, auto & oldValue, auto & newValue) {
std::cout << "I heard you changed " << RareTs::toStr<RareTs::remove_cvref_t<decltype(t)>>() << "." << member.name
<< " from " << oldValue << " to " << newValue << std::endl;
});
myTappedObj.myString = "poiuy";
myTappedObj.myInt = 1;
myTappedObj.myString = "qwerty";
std::cout << std::endl << Json::pretty(myObj) << std::endl;
}
struct MyObj
{
int myInt;
std::string myString;
REFLECT(MyObj, myInt, myString)
};
int main()
{
auto myObj = RareBuilder<MyObj>().myInt(1234).myString("asdf").build();
std::cout << Json::pretty(myObj) << std::endl;
}
class MyObj
{
int a;
int b;
public:
REFLECT(MyObj, a, b)
// Having a "bool validate()" method signals that private data should be buidlable unless RareTs::BuilderIgnore'd
bool validate() // You may use either a boolean, or a void method with exceptions, to perform validation
{
return a >= 0 && b >= 0; // Returns boolean indicating whether the built structure was valid
}
};
class MyOtherObj
{
int a;
int b;
NOTE(c, RareTs::BuilderIgnore)
int c = 0;
public:
REFLECT(MyOtherObj, a, b, c)
void validate() // Having a "void validate()" method signals that private data should be buidlable unless RareTs::BuilderIgnore'd
{
if ( a < 0 || b < 0 ) // Throws an exception indicating what was invalid
throw std::logic_error("a and b must both be non-negative!");
}
};
int main()
{
try {
RareBuilder<MyObj>().a(-1).b(1).build();
} catch ( std::exception & e ) {
std::cout << "boolean validator failed with message: " << e.what() << std::endl;
}
try {
RareBuilder<MyOtherObj>().a(6).b(-6).build();
} catch ( std::exception & e ) {
std::cout << "void validator failed with message: " << e.what() << std::endl;
}
auto myObj = RareBuilder<MyObj>().a(1).b(2).build();
std::cout << "Built MyObj: " << Json::pretty(myObj) << std::endl;
auto myOtherObj = RareBuilder<MyOtherObj>().a(3).b(4).build();
std::cout << "Built MyOtherObj: " << Json::pretty(myOtherObj) << std::endl;
//RareBuilder<MyOtherObj>().c(1).build(); // "c" was annotated with RareTs::BuilderIgnore so this is not a buildable member/is a compile error
}
struct Point
{
float x;
float y;
REFLECT(Point, x, y)
};
struct Descriptor
{
std::string name;
std::string address;
REFLECT(Descriptor, name, address)
};
struct Area
{
int id;
Descriptor descriptor;
std::vector<Point> points;
REFLECT(Area, id, descriptor, points)
};
int main()
{
auto area = RareBuilder<Area>()
.id(92)
.descriptor(RareBuilder<Descriptor>().name("some area").address("12345 main street").build()) // Can use a nested builder
.points({{1.1f, 2.2f}, {1.9f, 3.2f}, {1.3f, -2.5f}, {-4.2f, -1.2f}}) // You can pass ~anything you could assign to std::vector<Point>
.build();
std::cout << Json::pretty(area) << std::endl;
}
struct MyDao
{
long pk;
std::string name;
std::string description;
std::string lastModifiedBy;
REFLECT(MyDao, pk, name, description, lastModifiedBy)
};
struct MyDto
{
std::string name;
std::string description;
REFLECT(MyDto, name, description)
};
int main()
{
MyDao myDao { 19L, "some name", "some description", "admin" };
MyDto myDto = RareMapper::map<MyDto>(myDao);
std::cout << "Mapped from: " << Json::pretty(myDao) << std::endl;
std::cout << "To: " << Json::pretty(myDto) << std::endl;
std::cout << "Using matching reflected field names (since no more-specific mapping approach was provided)" << std::endl;
}
struct MyDao
{
long object_id;
std::string object_name;
std::string object_description;
std::string last_modified_by;
REFLECT(MyDao, object_id, object_name, object_description, last_modified_by)
};
struct MyDto
{
std::string name;
std::string description;
REFLECT(MyDto, name, description)
NOTE(ObjectMappings, RareMapper::createMapping<MyDao, MyDto>()
.object_name->name()
.object_description->description()
.bidirectional())
};
int main()
{
MyDao myDao { 19L, "some name", "some description", "admin" };
MyDto myDto = RareMapper::map<MyDto>(myDao);
std::cout << "Mapped from: " << Json::pretty(myDao) << std::endl;
std::cout << "To: " << Json::pretty(myDto) << std::endl;
std::cout << "Using the bidirectional mapping" << std::endl;
myDto.description = "updated description";
RareMapper::map(myDao, myDto);
std::cout << "Mapped from: " << Json::pretty(myDto) << std::endl;
std::cout << "To: " << Json::pretty(myDao) << std::endl;
std::cout << "Using the bidirectional mapping" << std::endl;
}
struct MyConstructible
{
std::string description;
};
struct MyAssignable
{
std::string description;
};
struct MyDto
{
std::string description;
MyDto() {}
MyDto(const MyConstructible &) : description("from constructor") {} // RareMapper will use converting constructors if present
MyDto & operator=(const MyAssignable &) { // RareMapper will use assignment operators if present
this->description = "from assignment";
return *this;
}
operator MyAssignable() const { return MyAssignable{"from conversion"}; } // RareMapper will use conversion operators if present
};
// Highly recommend using such C++ standard mapping methods to define any non-trivial mappings even if you use RareMapper::map to map between types
int main()
{
MyDto constructed = RareMapper::map<MyDto>(MyConstructible{});
std::cout << constructed.description << std::endl;
MyDto assigned = RareMapper::map<MyDto>(MyAssignable{});
std::cout << assigned.description << std::endl;
MyAssignable assignable = RareMapper::map<MyAssignable>(assigned);
std::cout << assignable.description << std::endl;
}
struct Point
{
NOTE(latitude, Json::Name{"lat"})
double latitude;
NOTE(longitude, Json::Name{"long"})
double longitude;
REFLECT(Point, latitude, longitude)
};
struct Area
{
std::string name;
std::vector<Point> points = {{0.0, 0.0}};
REFLECT(Area, name, points)
};
int main()
{
Area area {};
std::cout << Json::out(area) << std::endl; // Out contains no whitespace
std::cout << "Enter a new area: ";
std::cin >> Json::in(area);
std::cout << "Final area: " << Json::pretty(area) << std::endl; // Pretty is pretty-printed JSON
}
struct Point
{
NOTE(latitude, Json::Name{"lat"})
double latitude;
NOTE(longitude, Json::Name{"long"})
double longitude;
REFLECT(Point, latitude, longitude)
};
struct Area
{
std::string name = "B.C.";
std::vector<Point> points = {{0.0, 0.0}};
REFLECT(Area, name, points)
};
// Customizations must be in the global namespace
template <>
struct Json::Input::Customize<Point, RareTs::MemberType<Point>::latitude::type, RareTs::IndexOf<Point>::latitude>
{
static bool as(std::istream & is, Context &, const Point &, double & value)
{
is >> Json::in(value);
if ( value < -90 || value > 90 ) // Validating some input
throw std::logic_error("Latitudes must be between -90 and 90");
return true; // True indicates you did read the json field value off of is, false would indicate you want to fallback to the default json reader
}
};
template <>
struct Json::Output::Customize<Area, RareTs::MemberType<Area>::name::type, RareTs::IndexOf<Area>::name>
{
static bool as(Json::OutStreamType & os, Context &, const Area &, const std::string & value)
{
if ( value == "B.C." ) // Replacing some area name codes with the full area name
{
Json::Put::string(os, "British Columbia");
return true; // True indicates you did put some valid json output
}
return false; // False indicates you want to use the default output
}
};
int main()
{
Area area {};
std::cout << Json::out(area) << std::endl; // Out contains no whitespace
std::cout << "Enter a new area: ";
std::cin >> Json::in(area);
std::cout << "Final area: " << Json::pretty(area) << std::endl; // Pretty is pretty-printed JSON
}
struct MyObj {
int myInt = 0;
std::string myString;
Json::FieldCluster fieldCluster;
REFLECT(MyObj, myInt, myString, fieldCluster)
};
int main()
{
MyObj myObj{};
std::cout << Json::out(myObj) << std::endl;
std::cout << "Enter a new version of this object including additional fields: " << std::endl;
std::cin >> Json::in(myObj);
std::cout << Json::pretty(myObj) << std::endl;
if ( myObj.fieldCluster.object().size() > 0 )
std::cout << "I like your new field: " << myObj.fieldCluster.object().begin()->first << std::endl;
Json::Generic::Object anyObj{};
std::cout << std::endl << "Enter any object: ";
std::cin >> Json::in(anyObj);
std::cout << Json::pretty(anyObj);
}