1.2. Overview: How To... - TheNitesWhoSay/RareCpp GitHub Wiki

Reflecting Members:

Alternative reflection methods:

Notes / Annotations:

Reflecting Inheritance:

Reflecting Functions:

Reflecting Overloads:

Misc:

Adaptive Structures:

RareBuilder:

RareMapper:

RareJson:

Define, iterate & modify class members

source file Run

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");
    });
}

Access class members by constexpr index

source file Run

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;
}

Access class members by runtime index

source file Run

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;
    });
}

Access class members by name

source file Run

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;
    });
}

Get member access protection modifier

source file Run

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);
}

Get member metadata using identifier

source file Run

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;
}

Pack & fold class members

source file Run

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;
    });
}

Filter members using constexpr ifs

source file Run

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;
        }
    });
}

Filter members using predicate filtering

source file Run

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
    });
}

Reflect members outside of class

source file Run

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;
    });
}

Reflect private members outside of class

source file Run

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;
    });
}

Auto-reflect members of aggregates

source file Run

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;
    });
}

Reflect private members outside of class with notes

source file Run

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);
}

Member annotations

source file Run

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;
}

Class-level annotations

source file Run

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;
}

Anonymous annotations

source file Run

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;
}

Filtered notes has/get/forEach

source file Run

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;
}

Define & iterate super-classes

source file Run

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;
    });
}

Access super-class by constexpr index

source file Run

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;
}

Access super-class by runtime index

source file Run

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;
    });
}

Traversing inheritance trees

source file Run

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>();
}

Define, iterate & call functions

source file Run

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();
    });
}

Iteratively build arguments & call function

source file Run

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");
}

Define, iterate & call overloads

source file Run

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");
    });
}

Access known overload by constexpr index

source file Run

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;
}

Access known overload by runtime index

source file Run

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");
    });
}

Pack known overloads

source file Run

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), ...);
    });
}

Access unknown overload (non-noted argument set)

source file Run

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");
}

Working with cv-ref qualified overloads

source file Run

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);
    });
}

Type toStr

source file Run

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;
}

Runtime to constexpr index

source file Run

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;
    });
}

White-box testing

source file Run

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
}

Making tuples

source file Run

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;
}

Tuplifying using wrapper type

source file Run

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;
}

Directly tuplifying reflected types

source file ![Run][run]

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;
}

Clone reflected structure

source file ![Run][run]

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;
}

Typedefs per reflected structure member

source file ![Run][run]

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;
}

Implementing whiteboxer/reference structure

source file ![Run][run]

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;
}

Implementing builders

source file ![Run][run]

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;
}

Implementing change-listeners

source file ![Run][run]

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;
}

Simple builder

source file ![Run][run]

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;
}

Validated builders

source file ![Run][run]

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
}

Aggregated builders

source file ![Run][run]

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;
}

Trivial mapping

source file ![Run][run]

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;
}

Custom-field mapping

source file ![Run][run]

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;
}

Using standard mappings

source file ![Run][run]

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;
}

Basic JSON I/O

source file ![Run][run]

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
}

JSON customization

source file ![Run][run]

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
}

Unknown fields

source file ![Run][run]

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);
}
⚠️ **GitHub.com Fallback** ⚠️