1.4. Design Decisions - TheNitesWhoSay/RareCpp GitHub Wiki

Guiding Philosophy

RareCpp has many goals for itself, but some are considered more important than (or, are a "higher-order virtue") than others, these goals/virtues as well as their ordering helps to guide use of development time and make design decisions where there are conflicts. This ordering is not meant to be dogmatic, exceptions can be made, especially where a little sacrifice of a higher-order virtue makes a big difference to a lower-order virtue.

1st Order Virtues

  • Approachability - deliver an intuitive, beginner friendly library, available on the widest set of compilers and platforms possible with trivial setup
  • Completeness - deliver a rich interface with approachable solutions for common use-cases and with the capacity to handle uncommon use-cases
  • Consistency - deliver well-tested, consistent, minimally compiler-dependent behavior
  • Runtime Performance - deliver fast running code by using quality algorithms, testing runtime performance and fine-tuning codegen

2nd Order Virtues

  • Compile-time Performance - deliver fast-compilation, use quality compile-time algorithms, test and fine-tune header inclusion and utilization times
  • Quality - deliver a code-base free of warnings on high warning settings, using minimal warning suppression, apply individual discipline as well as tooling to maximize best-practices
  • External-Terseness - deliver terse interfaces without need for excessive scope resolution or identifiers longer than needed for intuitive use

3rd Order Virtues

  • Internal-Simplicity - deliver simple, modular code implementations wherever possible, parts of the code should be easily possible to copy-out and use in isolation
  • Internal-Terseness - deliver shorter sources where possible, regularly remove code as it becomes unused, prefer shorter code when it does not conflict with other virtues

Something to highlight is just how important approachability and completeness is regarded for RareCpp, a big impetus for the development of RareCpp was just how unapproachable other libraries for C++ reflection are - maybe they can only be used on compiler X, maybe they require advanced metaprogramming knowledge to use effectively, maybe they lack an important capability that reflection in other programming languages can get you, or maybe they're just difficult to setup and learn...

RareCpp starts from the notion that reflection shouldn't be hard to start using, it should be easy, and at the end of the day it should save you not just lines of code, but development time on the whole.

One more point worth highlighting: no 2nd or 3rd order virtue is ignored, their relatively lower priority doesn't preclude them having importance, and any virtue listed here has had time and testing devoted to it.


Design Decisions

Reflection First

RareCpp involves extensions which may deal with serialization, controllers, mappings, builders, and all sorts of cool things; but ultimately, RareCpp is a reflection library first, both design decisions and development time should prioritize a quality, generialized reflection system over any extension.

Minimal Macros, but yes, macros

Macros are loathsome, but for the sake of library simplicity, completeness, and availability on all compilers - without tons of repitition (e.g. having to mention a member name string, pointer, member adapter, etc.) and boilerplate - some macros are a must. Additionally while support for compiler-plugins will likely be explored eventually, the library will regardless support a macro-based solution to maximize portability and approachability.

Minimal Macro Content

As of RareCpp 2.0 content within the REFLECT macro is kept to an absolute minimum (while respecting the need for completeness), and all access to reflected values/information is through RareTs:: interfaces, this minimizes the compile-time overhead to add the REFLECT macro to an object (especially with the focus on providing templates, not complete functions/types), maximizes testability, maximizes interface flexibility, and with luck, eliminates the need to regularly alter macros.

No Feature Macros

RareCpp is intended to be used to make libraries/library-like code, and feature macros have the potential to place the diamond problem on steroids. While it would be possible to get some compile-time gains by having feature macros to disable certain features (or to have features disabled by default and enabled by macros) this wouldn't mesh well with dependency graphs nor would it mesh well with the goal of maximum approachability. If minimizing compile-time by removing certain features gains importance and would make a measurable difference... a quick guide for how to write your own compatible REFLECT macros and/or separate headers/namespaces should be employed.

Header Only

Approachability is in part, best achived by having header-only libraries, additionally RareCpp is almost entirely templated so code largely could only be appropriately placed in headers.

References & Lambdas, Not Getters and Setters

C++ lacks the capability to return a dynamic type based on runtime information (at best you can return something like std::variant which merely pushes back access of the value a step), therefore, any value getter would ultimately be a dissapointment in which you're explicitly mentioning the name, index, or type of the field you're getting directly in your code/at compile time. Alternatively, type-erasure could be used, but a type-erasure approach has runtime costs - that is you'd be paying twice for a single read (e.g. array access followed by polymorphic access/vtable use) - and you'd lose the highly advantagous nature of having a strong value type to work with. Setters wouldn't be so problematic, but of the get-set pair of operations setters are quite a bit less important and unlikely to be used often without getters. Using reference values in a lambda you get direct access, strong typing, and a unified interface where you can simply refer to the value.

Compile-time & Runtime Access

It's important that reflection information be available at compile-time in order for extensions to be able to generate the right code, but it's also often very important to be able to access members/supers based on runtime information (e.g. you read a field name off a JSON, you don't know what that field name will be at a given point in parsing a JSON document, you need to be able to access the associated member using the name you've read at runtime). While providing compile-time indexed access to members/supers technically would let the user write something to cover runtime access (in a switchy/manual way or with some reasonably advanced meta-programming knowledge); the relatively common need to perform such an access, as well as the goals for approachability, completeness, and to make code using RareCpp more consistent means RareCpp will directly provide the means to access such things by either compile-time indexes/identifiers or runtime indexes/identifiers.