gnioG kcaB nI emiT

Step 7 - gnioG kcaB nI emiT

This is the seventh step, adding c++20 compatability - each step adds or improves a small feature, this text is just highlighting a few details along the way, the best way to work through is to build and run each one, reading through the code with this description alongside. Similarly reading this alongside the diff for each step will also mean more than either just reading the text or just reading the code alone.

All the steps so far have required a C++26-compliant compiler with -freflection support. However, we might still want to be able to run this on slightly older compilers. We won't need to change the API, runtime logic or Python binding, all we need to do is use the old style meta-template expansion and populate our now well defined metadata dictionary and type structures.

The build change

Drop the freflection flag and switch to C++20:

CXXFLAGS = -O0 -g -Wall -Wextra -std=c++20 -fPIC -Ixplat -Idemo

We'll define and use XPLAT_HAS_REFLECTION to select between alternate macros for reflection.

Manual registration macros

Our C++26 macros are nice and compact:

#ifdef XPLAT_HAS_REFLECTION
XCLASS_REFLECT(Demo);
XSTRUCT_REFLECT(Location);
XSTRUCT_REFLECT(PersonInfo);
XSTRUCT_REFLECT(TestStruct);
XENUM_REFLECT(MyEnum);
#else
...
#endif

Without the C++26 refection though, we'll need to markup all the functions we want to expose manually:

XCLASS_BEGIN(Demo);
  XFUNC(getBool);
  XFUNC(getInt);
  XFUNC(getLong);
  XFUNC(getDouble);
  XFUNC(getString);
  XFUNC(getEnum);
  XFUNC(getDate);
  XFUNC(getVector);
  XFUNC(getMap);
  XFUNC(putInt);
  XFUNC(putDouble);
  XFUNC(putString);
  XFUNC(putVector);
  XFUNC(putMap);
  XFUNC(doStruct);
  XFUNC(getStruct);
  XFUNC(getPersonInfo);
  XFUNC(putPersonInfo);
  XFUNC(setAll);
XCLASS_END;

Each XFUNC(name) uses a templated register method, which uses template recursion and SFINAE to walk through the parameters, determine the types and then populate the metadata structures in the same manner as the C++26 version (although we don't know the parameter names, so we create 'fake' "arg1"-"argN" labels for them, another potential improvement is to enable the 'documentation' calls naming these):

#define XFUNC(FUNC)                                                                                
    xplat::registerMethod<&CurrentClass::FUNC>(#FUNC);
 ...->
//Walks through the method arguments
m.parameters = buildParameters<Args>(std::make_index_sequence<N>{});

if (current_class_name) {
    m.name = methodName;
    //Register metadata
    Registry::instance().addMethod<typename Traits::Class>(std::move(m));
}

Structs and enums follow the same pattern with explicit element defining:

XSTRUCT_BEGIN(Location);
  XSTRUCT_FIELD(city);
  XSTRUCT_FIELD(street);
  XSTRUCT_FIELD(number);
XSTRUCT_END;

XSTRUCT_BEGIN(PersonInfo);
  XSTRUCT_FIELD(name);
  XSTRUCT_FIELD(age);
  XSTRUCT_FIELD(address);  // nested struct, same as before
XSTRUCT_END;

XENUM_BEGIN(MyEnum);
  XENUM_VALUE(Red);
  XENUM_VALUE(Green);
  XENUM_VALUE(Blue);
XENUM_END;

XCLASS(Demo); // instantiates the static registrar

Unchanged

The libxplat internals can stay the same and are simply being populated from two different routes now - we can add addition routes in in future to augment the data but the C API remains the same, and so the Python binding is also unchanged. Compiling from C++20 or C++26 doesn't affect the target language bindings at all, we can move up and down toolchains without requiring any user side updates or changes.

Again, make run-python will run through the tests calls and show the same output as the previous step, we can toggle C++26 support on and off and see the same results.

There is a minor change required for the templated macro markup in the demo library, but there are a few ways we could streamline this, e.g. we could always use the verbose version of the macros and then call the refelect variants within making the _FUNC and _FIELD macros no-ops.

Popular posts from this blog

seven month update

Tracking running Part #2

Capsure RM200 hacking