Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:
- '**/*.md'
push:
branches: [ release ]
paths-ignore:
- '**/*.md'
workflow_dispatch:

jobs:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ on:
- '**/*.md'
push:
branches: [ release ]
paths-ignore:
- '**/*.md'
workflow_dispatch:

jobs:
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ rtl::CxxMirror& cxx::mirror() {
return cxx_mirror;
}
```
`cxx_mirror` is a immutable, stack-allocated, value-type object and is safe to copy.
However, when used as a singleton (as shown above), implicit copies (e.g., `auto mirror = cxx::mirror()`) can unintentionally violate the singleton semantics.
To prevent this, the `rtl::CxxMirror`'s copy constructor is restricted to avoid such unintended duplication.

### RTL in action:

**[Explore the demo code](https://github.com/ReflectCxx/RTL-Demo)**
Expand Down
2 changes: 1 addition & 1 deletion RTLTestRunApp/src/CxxMirrorTests/CxxMirrorObjectTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ namespace rtl_tests
// Two mirrors constructed from same set of registrations must serialize identically.
// Confirms stability of metadata and deterministic JSON output.
rtl::CxxMirror mirror = cxx_mirror();
rtl::CxxMirror mirror0 = mirror;
rtl::CxxMirror mirror0(mirror);
mirrorStr0 = rtl::CxxMirrorToJson::toJson(mirror0);
}
std::string mirrorStr1;
Expand Down
11 changes: 0 additions & 11 deletions RTLTestRunApp/src/RObjectTests/RObjectReflecting_stdSharedPtr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,6 @@
using namespace test_utils;
using namespace rtl;

namespace {

static rtl::CxxMirror cxx_mirror()
{
static rtl::CxxMirror m = rtl::CxxMirror({
rtl::type().record<int>("int").build(),
rtl::type().record<Node>("Node").build()
});
return m;
}
}

namespace rtl::unit_test
{
Expand Down
59 changes: 32 additions & 27 deletions ReflectionTemplateLib/rtl/inc/CxxMirror.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,52 +15,57 @@

namespace rtl
{
/* @class CxxMirror
* Provides the primary interface to access registered functions and methods by name.
* This is the single point of access to the entire reflection system.
/**
* @class CxxMirror
*
* All type registrations happen during object construction.
* @brief Primary interface for accessing all registered types, functions, and methods by name.
*
* Objects of this class are regular stack-allocated objects (non-singleton) and are destroyed automatically when they go out of scope.
* Copy constructor and assignment operator are deleted, instances can only be passed by reference or wrapped in a smart pointer.
* CxxMirror serves as the single point of entry to the entire reflection system.
* All type, function, and method registrations occur during object construction.
*
* All inherited members are properly destroyed when the object is destroyed, except for the *functor containers*.
* Instances of this class are regular, stack-allocated value-type objects.
* The copy constructor is explicit, and the assignment operator is deleted.
*
* Notes on Functor Storage:
* - Functor containers have static lifetime and are not part of this class or its base class.
* - This class (and its base) store only `Function` objects, which serve as hash-keys to look up actual functors.
* - Registering the same functor multiple times across different `CxxMirror` instances will not duplicate the functor in the container.
* - However, each `CxxMirror` instance will maintain its own unique `Function` hash-keys, even for the same functor.
* - Within a single `CxxMirror` object, registering the same functor multiple times is ignored (no duplicate `Function` hash-keys).
* The class stores only metadata in the form of strings and PODs.
* It does not own or manage any heap-allocated or static-lifetime resources.
*
* Summary:
* - Functor objects are shared and static.
* - `Function` keys are per-instance.
* - Functor storage remains unaffected by the number of `CxxMirror` instances.
*/
* Function pointers provided during registration are cached separately and are
* decoupled from this class. Each CxxMirror instance maintains its own metadata
* that references the registered function pointers.
*
* As a result, the same function pointer may be associated with different string
* keys across different CxxMirror instances without duplicating the underlying
* function pointer storage.
*
* Within a single CxxMirror instance, attempting to register the same functor
* multiple times is ignored, and a warning is emitted to the console.
*/
class CxxMirror : public detail::CxxReflection
{
public:

CxxMirror(CxxMirror&&) = default;
CxxMirror(const CxxMirror&) = default;
// avoids implicit move.
explicit CxxMirror(CxxMirror&&) = default;

// avoids implicit copy.
explicit CxxMirror(const CxxMirror&) = default;

// Constructs CxxMirror using a set of Function objects. All other constructors are disabled.
// Constructs CxxMirror using a set of Function objects.
explicit CxxMirror(const std::vector<Function>& pFunctions);

// Returns a Record containing function hash-keys for the given record ID.
std::optional<Record> getRecord(const std::size_t pRecordId) const;
// Returns a valid Record if the type is found by id; otherwise, std::nullopt.
std::optional<Record> getRecord(const traits::uid_t pRecordId) const;

// Returns a Record containing function hash-keys for the given record name.
// Returns a valid Record if the type is found by name in default namespace group; otherwise, std::nullopt.
std::optional<Record> getRecord(const std::string& pRecordName) const;

// Returns a Record containing function hash-keys for the given record name (overloaded for namespace support).
// Returns a valid Record if the type is found by name in the given namespace group; otherwise, std::nullopt.
std::optional<Record> getRecord(const std::string& pNameSpaceName, const std::string& pRecordName) const;

// Returns a Function object for the given function name (non-member function).
// Returns a valid Function if found by name in default namespace group; otherwise, std::nullopt.
std::optional<Function> getFunction(const std::string& pFunctionName) const;

// Returns a Function object for the given function name, within the specified namespace.
// Returns a valid Function if found by name in the given namespace group; otherwise, std::nullopt.
std::optional<Function> getFunction(const std::string& pNameSpaceName, const std::string& pFunctionName) const;
};
}
101 changes: 60 additions & 41 deletions ReflectionTemplateLib/rtl/src/CxxMirror.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,80 +15,99 @@

namespace rtl
{
/* @Constructor: CxxMirror
@params: 'const std::vector<Function>&'
* accepts vector of 'Function' objects, which are hash-key to lookup a functor.
* the only constructor to construct 'CxxMirror' object.
* Syntax for constructing - CxxMirror({ type().function("func_name").build(), ..., ... })
* '.build()' function will return a 'Function' object, and passed to std::vector initializer list.
* the vector is simply forwarded to the base class constructor.
*/ CxxMirror::CxxMirror(const std::vector<Function>& pFunctions) : detail::CxxReflection(pFunctions)
CxxMirror::CxxMirror(const std::vector<Function>& pFunctions) : detail::CxxReflection(pFunctions)
{
rtl::detail::ReflectedConversions::init();
}

/* @method: getRecord
@param: const std::string& (name of the class/struct)
@return: std::optional<Record>
* if the class/struct isn't found by the given name, std::nullopt is returned.
* every class/struct's is grouped under a namespace.
* if no namespace is specified while registration, NAMESPACE_GLOBAL is used.
*/ std::optional<Record> CxxMirror::getRecord(const std::string& pRecord) const
/**
* @method getRecord
*
* @param pRecordName The name of the class or struct to look up.
* @return std::optional<rtl::Record>
* Returns a valid Record if the type is found by name in default namespace group; otherwise, std::nullopt.
*
* All registered classes and structs are grouped under a namespace.
* If no namespace is specified during registration, NAMESPACE_GLOBAL(the default) is used. */
std::optional<Record> CxxMirror::getRecord(const std::string& pRecordName) const
{
return getRecord(std::string(detail::NAMESPACE_GLOBAL), pRecord);
return getRecord(std::string(detail::NAMESPACE_GLOBAL), pRecordName);
}

/* @method: getFunction
@param: const std::string& (name of the non-member function)
@return: std::optional<Function>
* if the function isn't found by the given name, std::nullopt is returned.
* every function is grouped under a namespace.
* if no namespace is specified while registration, NAMESPACE_GLOBAL is used.
*/ std::optional<Function> CxxMirror::getFunction(const std::string& pFunction) const
/**
* @method getFunction
*
* @param pFunctionName The name of the non-member function to look up.
* @return std::optional<rtl::Function>
* Returns a valid Function if found by name in default namespace group; otherwise, std::nullopt.
*
* All registered non-member functions are grouped under a namespace.
* If no namespace is specified during registration, NAMESPACE_GLOBAL(the default) is used. */
std::optional<Function> CxxMirror::getFunction(const std::string& pFunctionName) const
{
return getFunction(std::string(detail::NAMESPACE_GLOBAL), pFunction);
return getFunction(std::string(detail::NAMESPACE_GLOBAL), pFunctionName);
}

std::optional<Record> CxxMirror::getRecord(const std::size_t pRecordId) const
/**
* @method getRecord
*
* @param pRecordId The RTL-specific unique type identifier.
* @return std::optional<rtl::Record>
* Returns a valid Record if the type is found; otherwise, std::nullopt.
*
* Every registered type `T` is assigned a unique integer type ID, which can be
* obtained via `rtl::traits::uid<T>::value` and cached for efficient lookup.
*
* The primary benefit of using a type ID is that it avoids the need to provide
* the namespace and type name as strings during lookup. */
std::optional<Record> CxxMirror::getRecord(const traits::uid_t pRecordId) const
{
const auto& recordMap = getRecordIdMap();
const auto& itr = recordMap.find(pRecordId);
return (itr == recordMap.end() ? std::nullopt : std::make_optional(itr->second));
}

/* @method: getRecord
@param: std::string (namespace name), std::string (class/struct name)
@return: std::optional<Record>
* retrieves the class/struct (as Record) registered under the given namespace.
* if the class/struct isn't found by the given name, std::nullopt is returned.
*/ std::optional<Record> CxxMirror::getRecord(const std::string& pNameSpace, const std::string& pRecord) const
/**
* @method getRecord
*
* @param pNameSpaceName The namespace under which the type was registered.
* @param pRecordName The name of the type to look up.
* @return std::optional<Record>
* Returns a valid Record if the type is found by name in the given namespace group; otherwise, std::nullopt.
*
* Retrieves the class or struct registered under the specified namespace. */
std::optional<Record> CxxMirror::getRecord(const std::string& pNameSpaceName, const std::string& pRecordName) const
{
const auto& nsRecordMap = getNamespaceRecordMap();
const auto& itr = nsRecordMap.find(pNameSpace);
const auto& itr = nsRecordMap.find(pNameSpaceName);
if (itr != nsRecordMap.end())
{
const auto& recordMap = itr->second;
const auto& itr0 = recordMap.find(pRecord);
const auto& itr0 = recordMap.find(pRecordName);
if (itr0 != recordMap.end()) {
return std::make_optional(itr0->second);
}
}
return std::nullopt;
}

/* @method: getFunction
@param: namespace name (std::string), non-mermber function name (std::string)
@return: std::optional<Function>
* retrieves the function (as 'Function' object) registered under the given namespace.
* if the function isn't found by the given name, std::nullopt is returned.
*/ std::optional<Function> CxxMirror::getFunction(const std::string& pNameSpace, const std::string& pFunction) const
/**
* @method getFunction
*
* @param pNameSpaceName The namespace under which the function was registered.
* @param pFunctionName The name of the function to look up.
* @return std::optional<Function>
* Returns a valid Function if found by name in the given namespace group; otherwise, std::nullopt.
*
* Retrieves the non-member function registered under the specified namespace. */
std::optional<Function> CxxMirror::getFunction(const std::string& pNameSpaceName, const std::string& pFunctionName) const
{
const auto& nsFunctionMap = getNamespaceFunctionsMap();
const auto& itr = nsFunctionMap.find(pNameSpace);
const auto& itr = nsFunctionMap.find(pNameSpaceName);
if (itr != nsFunctionMap.end())
{
const auto& functionMap = itr->second;
const auto& itr0 = functionMap.find(pFunction);
const auto& itr0 = functionMap.find(pFunctionName);
if (itr0 != functionMap.end()) {
return std::make_optional(itr0->second);
}
Expand Down