Compile-Time Reflection in C++ for use with Scripting Languages

Introspective

Introspective is a header file that brings reflection to any class that wants it, regardless of whether the reflected member is a constant, a static variable or a instance member function. It records declaration order, (function) type and address and passes them along unchanged during compile-time, with the ultimate goal of making the interaction with embedded scripting languages like Lua a little less of a hassle.

Compile-time reflection

Let's do this. cracks knuckles

(allTheMembers); for(auto briefs: luaReadyFns) { std::cout << briefs.Name << "; " << briefs.ErasedSig << " at address " << briefs.Fn << std::endl; lua_CFunction f = briefs.Fn; // Typechecks! } } // Check out the examples, it demonstrates integration with C++ (multiple) inheritance // and how to transfer recorded functions automatically to Lua! ">
#include <string>
#include <concepts>
#include <iostream>
#include <introspective.h>

using namespace introspective;

struct Reflective: Introspective
   
{
    
   MemDecl(
   static 
   constexpr Pie, 
   const 
   double) = 
   3.14;
    
   MemDecl(strung, std::string);
    
   double value;
    
    
   // Your standard run-of-the-mill functions
    
   FnDecl(add, (
   int x,    
   int y)    -> 
   int   ) { 
   return x + y; }
    
   FnDecl(sub, (
   double x, 
   double y) -> 
   double) { 
   return x - y; }
    
    
   // Overloads, you say? No problem.
    
   FnDecl(
   virtual mul, (
   double y)           -> 
   double) { 
   return value * y; }
    
   FnDecl(
   virtual mul, (
   int y)              -> 
   double) { 
   return 
   2 * value * y; }
    
   FnDecl(
   static  div, (
   double x, 
   double y) -> 
   double);  
   // for later...
    
    
   // Template member functions. C++-20 ready!
    
   FnDecl(
   constexpr TemplattedDiv,
           
   template(
   auto x, 
   auto y), 
           requires(std::integral
   
     && std::integral
    
     ),
           () -> decltype(x / y))
    {
        
     return x / y;
    }

    
     Reflective(std::string str, 
     double val): strung(str), value(val) {}
};


     double 
     Reflective::div(
     double x, 
     double y) { 
     return x / y; }  
     // Define later!


     int 
     main()
{
    
     // Declaration order is preserved in the indices.
    
     constexpr 
     auto addFnPtr         = Reflective::GetMemberByIndex<
     2>().
     Stencilled();
    
     constexpr 
     auto overloadedMulPtr = Reflective::GetMemberByIndex<
     4>().
     Stencilled();
    
     constexpr 
     auto templattedPtr    = Reflective::GetMemberByIndex<
     Reflective::GetReflectiveMemberCount() - 
     1>()
                                                 .
     template 
     Stencilled<
     265, 
     5>();
    Reflective refl{ 
     "Hello World!", 
     2.71 };
    std::cout << (refl.*addFnPtr)(
     5, 
     7)  << (refl.*overloadedMulPtr)(Reflective::Pie)
              << (refl.*templattedPtr)() << std::endl;

    
     // Compile-time list of all "member metas", objects on which you may call .Stencilled()
    
     // to get the final pointer/pointer-to-member.
    
     constexpr 
     auto allTheMembers = 
     Reflective::GetMembers();
    
    
     // Ready-to-use lua_CFunctions for Lua scripting, fresh from the oven!
    
     constexpr 
     auto luaReadyFns = introspective::MarshalledFns
     
      (allTheMembers);
    
    
      for(
      auto briefs: luaReadyFns)
    {
        std::cout << briefs.
      Name << 
      "; " << briefs.
      ErasedSig
                  <<      
      " at address " << briefs.
      Fn        << std::endl;
        lua_CFunction f = briefs.
      Fn;  
      // Typechecks!
    }
}


      // Check out the examples, it demonstrates integration with C++ (multiple) inheritance

      // and how to transfer recorded functions automatically to Lua!
     
    
   
  

Interaction with scripting languages

If you can't find your language in the examples, don't worry: this header is scripting-language agnostic and can be made to work with any one that is written in C. The specifications for the marshalling interface that you need to build are listed here:

  • template static auto FromEmbedded(MarshallArgs..., std::size_t where) . Extracts one value of type Data through the facilities exposed in MarshallArgs..., and returns that value. Whether you return a Data value by copy, by reference or const-qualified is your choice; the only thing this function template needs to satisfy are the needs of the wrapped functions. The isStaticCall flag indicates whether the embedded script is trying to call a static function or an instance function (some scripting languages make an explicit difference between those two).

This function is always invoked when the scripting language wants to make a call to the wrapped function. where tells the position of the argument it needs to be in for the call to the wrapped function to make any sense - if the wrapped function requires a double as its first argument and a std::string as its second, then FromEmbedded will be asked to extract a double with where = 0 and a std::string with where = 1.

  • template static «Return Type» ToEmbedded(MarshallArgs..., Data data) . Marshalls data back to a representation that the MarshallArgs... facilities can understand again. Called when the wrapped function returns a value. That value will be provided with data.

Wrapped functions returning void cause the marshalling bridge to not call ToEmbedded, since there is no data to marshall back. «Return Type» needs to be the same type as the return type in MarshallSig.

  • static «Return Type» ToEmbedded(MarshallArgs...). Same as the other overload of ToEmbedded, except that this overload is called when the wrapped function returns void.

  • template static bool PrepareExtraction(MarshallArgs...) . Called to inform the MarshallArgs... to prepare for extraction of DataArgTypes... in that specific order. Returns a bool indicating the readiness and the ability to extract these arguments. This function exists to enable restrictions on types that may be marshalled and to make type checking on the incoming arguments possible.

  • static «Return Type» FailExtracted(MarshallArgs...). Called when PrepareExtraction returns false. As above, «Return Type» needs to be the same type as the return type in MarshallSig. The value returned from this function will be the value returned from the wrapper function.

Once such a specialisation has been written, all that's left to do to get the desired functions is

// The returned value is a std::array, and its length depends on the number of members declared with
// the Introspective macros.
constexpr auto scriptReadyFnArray = introspective::MarshalledFns
   («Introspective Type»::GetMembers());
  

That array will contain introspective::FnBrief elements, where the first element in such a pair is the name of the wrapped function and the second element is a pointer to a function with signature MarshallSig which automatically converts arguments that are provided inside the embedded scripting language to C++ arguments and feeds them to the wrapped function in the correct order, using the five functions described above.

Take a look at the examples for more details.

Additional member detection in classes

As a bonus, the header also provides some short macros for detecting a specific member in an unspecified generic class. I'll mention them here briefly.

  • HasMember(InType, member, ...) -> bool. Indicates true if member member can be found in type InType, regardless of whether the member is a function or a variable. Actually returns either std::true_type or std::false_type, but these are implicitly convertible to bool in any context. The varargs must be filled with matching parameter types if the subject of the search is a function with specific parameters.
  • GetStaticMember(InType, member, MemberType) -> MemberType*. Returns a pointer to the static variable InType::member, if one such exists, otherwise nullptr. MemberType may also be a function type, as in int(std::string, double) without pointer notation. Actually returns either a valid value of MemberType* or a std::nullptr_t, depending on the existence of the member.
  • GetStaticConstant(InType, member, MemberType) -> const MemberType*. Same as GetStaticMember, but enforces const-ness of the member. If the member is not const-qualified, returns nullptr.
  • GetObjectMember(InType, member, MemberType) -> MemberType InType::*. Same as the static version, but returns a pointer-to-member. Does not support object member functions at the moment.

These macros may be used inside a function and may be treated as such, they only hide two lines of template boilerplate code. As a consequence of template metaprogramming, these macros also support template type parameters as their argument.

Requirements

Fairly thin; the header file only depends on the standard library. However, it is written for C++20 and uses some features that have been introduced with that or the previous revision:

  • __VA_OPT__
  • Structural types as non-type template parameters
  • Lambda literals in unevaluated contexts
  • Concepts for a little better error tracing.
  • Default-constructible lambda types where their closure is equal to itself.
  • consteval for making sure none of the reflection algorithms spill over into the runtime.
  • Fold expressions for variadic template arguments (might have been already introduced with C++17, mentioned for the sake of completeness)

This header has been tested with recent versions of g++-11 and clang++ 13.0.0; other compilers may or may not work. Note that clang 13.0.0 has not been released yet, this necessitates building clang 13.0.0 yourself from source. Observe that current release versions of clang 12.0.x can't compile this header, as they lack support for some C++20 constructs used here.

Until C++ implements some real universal reflection, this header ought to do it for the time being.

Any feedback or contribution is greatly appreciated!

Owner
Josip Palavra
Looking for work!
Josip Palavra
Similar Resources

Entity-Component-System (ECS) with a focus on ease-of-use, runtime extensibility and compile-time type safety and clarity.

Entity-Component-System (ECS) with a focus on ease-of-use, runtime extensibility and compile-time type safety and clarity.

Kengine The Koala engine is a type-safe and self-documenting implementation of an Entity-Component-System (ECS), with a focus on runtime extensibility

Sep 22, 2022

A compiling time static reflection framework for C++

static_reflect This is a fully compiling time static reflection lightweight framework for C++. It provides a very rich compile-time reflection functio

Sep 5, 2022

Meta - static reflection tools for c++. i mostly use this with entt.

meta Static reflection tools for C++. I use it with EnTT but it can work with anything. The main features the library provides are: Registering types

Jul 12, 2022

C++ compile-time enum to string, iteration, in a single header file

C++ compile-time enum to string, iteration, in a single header file

Better Enums Reflective compile-time enum library with clean syntax, in a single header file, and without dependencies. In C++11, everything can be us

Sep 19, 2022

A Compile time PCRE (almost) compatible regular expression matcher.

Compile time regular expressions v3 Fast compile-time regular expressions with support for matching/searching/capturing during compile-time or runtime

Sep 22, 2022

StrCrypt Compile-time string crypter library for C++

StrCrypt  Compile-time string crypter library for C++

StrCrypt Compile-time string crypter library for C++ Having plain strings stored in the binary file or in memory can help reversering attempts to be m

Jun 26, 2022

C++20 compile time compressed string tables

Squeeze - C++20 Compile time string compression Experiments in building complex compile time executed code using constexpr functions to generate a com

May 29, 2022

cavi is an open-source library that aims to provide performant utilities for closed hierarchies (i.e. all class types of the hierarchy are known at compile time).

cavi cavi is an open-source library that aims to provide performant utilities for closed hierarchies (i.e. all class types of the hierarchy are known

Mar 9, 2022

a compile-time, header-only, dimensional analysis and unit conversion library built on c++14 with no dependencies.

UNITS A compile-time, header-only, dimensional analysis library built on c++14 with no dependencies. Get in touch If you are using units.h in producti

Sep 19, 2022

Compile-time String to Byte Array

STB Compile-time String to Byte Array. Why? You may ask, why'd you want to do this? Well, this is a common issue in the cheat development scene, where

May 7, 2022

obfuscated any constant encryption in compile time on any platform

obfuscated any constant encryption in compile time on any platform

oxorany 带有混淆的编译时任意常量加密 English 介绍 我们综合了开源项目ollvm、xorstr一些实现思路,以及c++14标准中新加入的constexpr关键字和一些模板的知识,完成了编译时的任意常量的混淆(可选)和加密功能。

Sep 19, 2022

Header-only compile time key-value map written in C++20.

C++ Static Map Header-only compile time key-value map written in C++20. Getting Started Simply add the files in your source and #include "@dir/Static_

Oct 19, 2021

lightweight, compile-time and rust-like wrapper around the primitive numerical c++ data types

prim_wrapper header-only, fast, compile-time, rust-like wrapper around the primitive numerical c++ data types dependencies gcem - provides math functi

Oct 22, 2021

compile time symbolic differentiation via C++ template expressions

SEMT - Compile-time symbolic differentiation via C++ templates The SEMT library provides an easy way to define arbitrary functions and obtain their de

Apr 8, 2022

Modern C++ 20 compile time OpenAPI parser and code generator implementation

OpenApi++ : openapipp This is a proof of concept, currently under active work to become the best OpenAPI implementation for C++. It allows compile tim

Aug 20, 2022

Instant compile time C++ 11 metaprogramming library

Brigand Meta-programming library Introduction Brigand is a light-weight, fully functional, instant-compile time C++ 11 meta-programming library. Every

Sep 22, 2022

Ctpg - Compile Time Parser Generator

Ctpg - Compile Time Parser Generator is a C++ single header library which takes a language description as a C++ code and turns it into a LR1 table parser with a deterministic finite automaton lexical analyzer, all in compile time.

Sep 13, 2022

DimensionalAnalysis - A compact C++ header-only library providing compile-time dimensional analysis and unit awareness

Dimwits ...or DIMensional analysis With unITS is a C++14 library for compile-time dimensional analysis and unit awareness. Minimal Example #include i

Jul 8, 2022
is a c++20 compile and runtime Struct Reflections header only library.

is a c++20 compile and runtime Struct Reflections header only library. It allows you to iterate over aggregate type's member variables.

Apr 18, 2022
A C header that allow users to compile brainfuck programs within a C compiler.

brainfuck.h A C header that allow users to compile brainfuck programs within a C compiler. You can insert the header into the top of your brainfuck so

Apr 18, 2022
data compression library for embedded/real-time systems

heatshrink A data compression/decompression library for embedded/real-time systems. Key Features: Low memory usage (as low as 50 bytes) It is useful f

Sep 23, 2022
A compile-time enabled Modern C++ library that provides compile-time dimensional analysis and unit/quantity manipulation.

mp-units - A Units Library for C++ The mp-units library is the subject of ISO standardization for C++23/26. More on this can be found in ISO C++ paper

Sep 21, 2022
Selective Compile-Time Reflection for C++

Introspective Some quotes from StackOverflow regarding reflection in C++: "Inspection by iterating over members of a type, enumerating its methods and

Mar 10, 2022
A modern compile-time reflection library for C++ with support for overloads, templates, attributes and proxies

refl-cpp v0.12.1 Documentation refl-cpp encodes type metadata in the type system to allow compile-time reflection via constexpr and template metaprogr

Sep 15, 2022
Game Scripting Languages benchmarked

Scriptorium ?? Game Scripting Languages benchmarked. Using latest versions at the time of writing (Jul 2015) Total solutions evaluated: 50 Results Ran

Sep 15, 2022
Notepad++ is a free source code editor and Notepad replacement that supports several programming languages and natural languages

Npp / Notepad++ is my customized text editor highly enhanced for coding such as insta-run, much more file extensions made self-recognizable, logically colored syntax highlighting for nearly every programming language and designed for very easy customizability -- from the toolbar, context menu, syntax coloring, plug-ins for optional increased capabilities and much more

Jan 23, 2022
Languages that compile to Lua

lua-languages Languages that compile to Lua Lua is famously and deceptively simple and enables many different programming paradigms. Like Javascript,

Sep 20, 2022