diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..68304f36 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,63 @@ +name: RTL Build + +on: + push: + branches: [ release ] + paths-ignore: + - '**/*.md' + pull_request: + branches: [ release ] + paths-ignore: + - '**/*.md' + workflow_dispatch: + +jobs: + build: + name: Build (${{ matrix.os }} / ${{ matrix.compiler }}) + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + compiler: [gcc, clang, msvc] + exclude: + - os: windows-latest + compiler: gcc + - os: windows-latest + compiler: clang + - os: ubuntu-latest + compiler: msvc + steps: + - name: Checkout source + uses: actions/checkout@v4 + + # Linux builds + - name: Install dependencies (Linux) + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt install -y ninja-build g++ clang + + - name: Configure (Linux) + if: runner.os == 'Linux' + run: | + if [ "${{ matrix.compiler }}" = "gcc" ]; then + cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++; + else + cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++; + fi + + # Windows builds (MSVC) + - name: Configure (Windows / MSVC) + if: runner.os == 'Windows' + run: cmake -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release + + # Build + - name: Build + run: cmake --build build --config Release --parallel + + # Upload artifacts per compiler + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: rtl-test-binaries-${{ matrix.compiler }} + path: bin/ \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ae7d28bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +################################################################################ +# This .gitignore file was automatically created by Microsoft(R) Visual Studio. +################################################################################ + +/build +/bin +/.vscode \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 012c1041..0500cf1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,9 @@ project(CxxReflectionProject) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin") # Add the subdirectories -add_subdirectory(CxxTestProject) -add_subdirectory(ReflectionTemplateLib) -add_subdirectory(CxxTypeRegistration) -add_subdirectory(CxxReflectionTests) \ No newline at end of file +add_subdirectory(CxxTestRegistration) +add_subdirectory(RTLTestRunApp) +add_subdirectory(RTLBenchmarkApp) +add_subdirectory(CxxTestProps) +add_subdirectory(CxxTestUtils) +add_subdirectory(ReflectionTemplateLib) \ No newline at end of file diff --git a/CxxReflectionTests/CMakeLists.txt b/CxxReflectionTests/CMakeLists.txt deleted file mode 100644 index 7c7ec00a..00000000 --- a/CxxReflectionTests/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -# CMakeLists.txt for CxxReflectionTests -cmake_minimum_required(VERSION 3.20) - -set(CMAKE_CXX_STANDARD 20) - -project(CxxReflectionTests) - -set(CXX_EXE_NAME CxxReflectionTests) -add_executable(${CXX_EXE_NAME} "") - - -include(FetchContent) -FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip -) -# For Windows: Prevent overriding the parent project's compiler/linker settings -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) - -include_directories(inc) -include_directories("${CMAKE_SOURCE_DIR}/CxxTypeRegistration/inc") -include_directories("${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/common") -INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/detail/inc") -include_directories("${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/access/inc") -include_directories("${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/builder/inc") - -target_link_libraries(${CXX_EXE_NAME} GTest::gtest_main) -target_link_libraries(${CXX_EXE_NAME} CxxTypeRegistration) -target_link_libraries(${CXX_EXE_NAME} ReflectionTemplateLib) - -# Add the source directory -include(src/CMakeLists.txt) - -include(GoogleTest) -gtest_discover_tests(${CXX_EXE_NAME}) diff --git a/CxxReflectionTests/src/CMakeLists.txt b/CxxReflectionTests/src/CMakeLists.txt deleted file mode 100644 index 0fc63a9b..00000000 --- a/CxxReflectionTests/src/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -# CMakeLists.txt for CxxReflectionTests -cmake_minimum_required(VERSION 3.20) - -project(CxxReflectionTests) - -# Create a variable containing the source files for your target -set(LOCAL_SOURCES - "${CMAKE_CURRENT_LIST_DIR}/ClassMethodsTests.cpp" - "${CMAKE_CURRENT_LIST_DIR}/ConstMethodOverloadTests.cpp" - "${CMAKE_CURRENT_LIST_DIR}/ConstructorTests.cpp" - "${CMAKE_CURRENT_LIST_DIR}/CopyConstructorTests.cpp" - "${CMAKE_CURRENT_LIST_DIR}/NameSpaceGlobalsTests.cpp" - "${CMAKE_CURRENT_LIST_DIR}/ReflectedCallStatusErrTests.cpp" - "${CMAKE_CURRENT_LIST_DIR}/StaticMethodTests.cpp" -) - -# Add any additional source files if needed -target_sources(CxxReflectionTests - PUBLIC - "${LOCAL_SOURCES}" -) \ No newline at end of file diff --git a/CxxReflectionTests/src/ClassMethodsTests.cpp b/CxxReflectionTests/src/ClassMethodsTests.cpp deleted file mode 100644 index adf38aad..00000000 --- a/CxxReflectionTests/src/ClassMethodsTests.cpp +++ /dev/null @@ -1,199 +0,0 @@ -#include - -#include "MyReflection.h" -#include "TestUtilsBook.h" - -using namespace std; -using namespace rtl; -using namespace rtl::access; -using namespace test_utils; - -namespace rtl_tests -{ - TEST(RTLInterfaceCxxMirror, get_class_methods_with_wrong_names) - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classBook = cxxMirror.getRecord(book::class_); - ASSERT_TRUE(classBook); - - optional badMethod = classBook->getMethod("no_method"); - EXPECT_FALSE(badMethod.has_value()); - } - - - TEST(ReflectionMethodCall, wrong_args) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classBook = cxxMirror.getRecord(book::class_); - ASSERT_TRUE(classBook); - - optional setAuthor = classBook->getMethod(book::str_setAuthor); - ASSERT_TRUE(setAuthor); - - auto [status, bookObj] = classBook->instance(); - - ASSERT_TRUE(status); - ASSERT_FALSE(bookObj.isEmpty()); - ASSERT_FALSE(setAuthor->hasSignature()); - - RStatus rStatus = (*setAuthor)(bookObj)(book::AUTHOR); - - ASSERT_TRUE(rStatus == rtl::Error::SignatureMismatch); - ASSERT_FALSE(rStatus.getReturn().has_value()); - EXPECT_FALSE(book::test_method_setAuthor(bookObj.get())); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ClassBookMethod, args_void) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classBook = cxxMirror.getRecord(book::class_); - ASSERT_TRUE(classBook); - - optional getPublishedOn = classBook->getMethod(book::str_getPublishedOn); - ASSERT_TRUE(getPublishedOn); - - auto [status, bookObj] = classBook->instance(); - - ASSERT_TRUE(status); - ASSERT_FALSE(bookObj.isEmpty()); - ASSERT_TRUE(getPublishedOn->hasSignature()); - - RStatus rStatus = (*getPublishedOn)(bookObj)(); - - ASSERT_TRUE(rStatus); - ASSERT_TRUE(rStatus.getReturn().has_value()); - ASSERT_TRUE(rStatus.isOfType()); - - const std::string& retStr = any_cast(rStatus.getReturn()); - EXPECT_TRUE(book::test_method_getPublishedOn_return(retStr)); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ClassBookMethod, args_string) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classBook = cxxMirror.getRecord(book::class_); - ASSERT_TRUE(classBook); - - optional setAuthor = classBook->getMethod(book::str_setAuthor); - ASSERT_TRUE(setAuthor); - - auto [status, bookObj] = classBook->instance(); - - ASSERT_TRUE(status); - ASSERT_FALSE(bookObj.isEmpty()); - ASSERT_TRUE(setAuthor->hasSignature()); - - RStatus rStatus = (*setAuthor)(bookObj)(std::string(book::AUTHOR)); - - ASSERT_TRUE(rStatus); - ASSERT_FALSE(rStatus.getReturn().has_value()); - - EXPECT_TRUE(book::test_method_setAuthor(bookObj.get())); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ClassBookMethodOverload, args_void) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classBook = cxxMirror.getRecord(book::class_); - ASSERT_TRUE(classBook); - - optional updateBookInfo = classBook->getMethod(book::str_updateBookInfo); - ASSERT_TRUE(updateBookInfo); - - auto [status, bookObj] = classBook->instance(); - - ASSERT_TRUE(status); - ASSERT_FALSE(bookObj.isEmpty()); - ASSERT_TRUE(updateBookInfo->hasSignature()); - - RStatus rStatus = (*updateBookInfo)(bookObj)(); - - ASSERT_TRUE(rStatus); - ASSERT_FALSE(rStatus.getReturn().has_value()); - EXPECT_TRUE(book::test_method_updateBookInfo(bookObj.get())); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ClassBookMethodOverload, args_string_double_charPtr) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classBook = cxxMirror.getRecord(book::class_); - ASSERT_TRUE(classBook); - - optional updateBookInfo = classBook->getMethod(book::str_updateBookInfo); - ASSERT_TRUE(updateBookInfo); - - auto [status, bookObj] = classBook->instance(); - - ASSERT_TRUE(status); - ASSERT_FALSE(bookObj.isEmpty()); - const bool signatureValid = updateBookInfo->hasSignature(); - ASSERT_TRUE(signatureValid); - - RStatus rStatus = (*updateBookInfo)(bookObj)(string(book::AUTHOR), book::PRICE, book::TITLE); - - ASSERT_TRUE(rStatus); - ASSERT_FALSE(rStatus.getReturn().has_value()); - const bool isSuccess = book::test_method_updateBookInfo(bookObj.get()); - EXPECT_TRUE(isSuccess); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ClassBookMethodOverload, args_charPtr_double_string) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classBook = cxxMirror.getRecord(book::class_); - ASSERT_TRUE(classBook); - - optional updateBookInfo = classBook->getMethod(book::str_updateBookInfo); - ASSERT_TRUE(updateBookInfo); - - auto [status, bookObj] = classBook->instance(); - - ASSERT_TRUE(status); - ASSERT_FALSE(bookObj.isEmpty()); - const bool signatureValid = updateBookInfo->hasSignature(); - ASSERT_TRUE(signatureValid); - - RStatus rStatus = (*updateBookInfo)(bookObj)(book::TITLE, book::PRICE, string(book::AUTHOR)); - - ASSERT_TRUE(rStatus); - ASSERT_FALSE(rStatus.getReturn().has_value()); - const bool isSuccess = book::test_method_updateBookInfo(bookObj.get()); - EXPECT_TRUE(isSuccess); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } -} \ No newline at end of file diff --git a/CxxReflectionTests/src/ConstMethodOverloadTests.cpp b/CxxReflectionTests/src/ConstMethodOverloadTests.cpp deleted file mode 100644 index f6d80fd7..00000000 --- a/CxxReflectionTests/src/ConstMethodOverloadTests.cpp +++ /dev/null @@ -1,226 +0,0 @@ -#include - -#include "MyReflection.h" -#include "TestUtilsPerson.h" - -using namespace std; -using namespace rtl::access; -using namespace test_utils; - -namespace rtl_tests -{ - TEST(ConstMethodOverload, const_method_no_overload_call_on_non_const_target) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional recOpt = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(recOpt.has_value()); - - const Record& classPerson = recOpt.value(); - optional updateLastName = classPerson.getMethod(person::str_updateLastName); - ASSERT_TRUE(updateLastName); - - auto [status, personObj] = classPerson.instance(string(person::FIRST_NAME)); - - ASSERT_TRUE(status); - ASSERT_FALSE(personObj.isEmpty()); - ASSERT_FALSE(personObj.isConst()); - ASSERT_TRUE(updateLastName->hasSignature()); - - const RStatus& rStatus = (*updateLastName)(personObj)(string(person::LAST_NAME)); - - ASSERT_TRUE(rStatus); - EXPECT_TRUE(person::test_method_updateLastName(personObj.get())); - } - EXPECT_TRUE(person::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ConstMethodOverload, const_method_no_overload_call_on_const_target) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional recOpt = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(recOpt.has_value()); - - const Record& classPerson = recOpt.value(); - optional updateLastName = classPerson.getMethod(person::str_updateLastName); - ASSERT_TRUE(updateLastName); - - auto [status, personObj] = classPerson.instance(string(person::FIRST_NAME)); - - ASSERT_TRUE(status); - ASSERT_FALSE(personObj.isEmpty()); - - personObj.makeConst(); - ASSERT_TRUE(personObj.isConst()); - ASSERT_TRUE(updateLastName->hasSignature()); - - const RStatus& rStatus = (*updateLastName)(personObj)(string(person::LAST_NAME)); - - ASSERT_TRUE(rStatus); - EXPECT_TRUE(person::test_method_updateLastName_const(personObj.get())); - } - EXPECT_TRUE(person::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ConstMethodOverload, const_method_no_overload_call_on_const_target_returns_string) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional recOpt = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(recOpt.has_value()); - - const Record& classPerson = recOpt.value(); - optional updateLastName = classPerson.getMethod(person::str_updateLastName); - ASSERT_TRUE(updateLastName); - - const std::string firstName = person::FIRST_NAME; - auto [status, personObj] = classPerson.instance(firstName); - - ASSERT_TRUE(status); - ASSERT_FALSE(personObj.isEmpty()); - - personObj.makeConst(); - ASSERT_TRUE(personObj.isConst()); - - optional getFirstName = classPerson.getMethod(person::str_getFirstName); - ASSERT_TRUE(getFirstName); - - const RStatus& rstatus = getFirstName->on(personObj).call(); - ASSERT_TRUE(rstatus); - ASSERT_TRUE(rstatus.isOfType()); - - const std::string retStr = std::any_cast(rstatus.getReturn()); - ASSERT_EQ(retStr, firstName); - } - EXPECT_TRUE(person::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ConstMethodOverload, const_method_string_call_on_const_target) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classPerson = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(classPerson); - - optional updateAddress = classPerson->getMethod(person::str_updateAddress); - ASSERT_TRUE(updateAddress); - - string fnameStr = person::FIRST_NAME; - auto [status, personObj] = classPerson->instance(fnameStr); - - ASSERT_TRUE(status); - ASSERT_FALSE(personObj.isEmpty()); - - personObj.makeConst(); - ASSERT_TRUE(personObj.isConst()); - ASSERT_TRUE(updateAddress->hasSignature()); - - const RStatus& rStatus = (*updateAddress)(personObj)(string(person::ADDRESS)); - - ASSERT_TRUE(rStatus); - EXPECT_TRUE(person::test_method_updateAddress_const(personObj.get())); - } - EXPECT_TRUE(person::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ConstMethodOverload, const_method_string_call_on_non_const_target) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classPerson = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(classPerson); - - optional updateAddress = classPerson->getMethod(person::str_updateAddress); - ASSERT_TRUE(updateAddress); - - auto [status, personObj] = classPerson->instance(string(person::FIRST_NAME)); - - ASSERT_TRUE(status); - ASSERT_FALSE(personObj.isEmpty()); - ASSERT_FALSE(personObj.isConst()); - ASSERT_TRUE(updateAddress->hasSignature()); - - const RStatus& rStatus = (*updateAddress)(personObj)(string(person::ADDRESS)); - - ASSERT_TRUE(rStatus); - EXPECT_TRUE(person::test_method_updateAddress(personObj.get())); - } - EXPECT_TRUE(person::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ConstMethodOverload, const_method_no_args_call_on_const_target) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional recOpt = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(recOpt.has_value()); - - const Record& classPerson = recOpt.value(); - optional updateAddress = classPerson.getMethod(person::str_updateAddress); - ASSERT_TRUE(updateAddress); - - auto [status, personObj] = classPerson.instance(string(person::FIRST_NAME)); - - ASSERT_TRUE(status); - ASSERT_FALSE(personObj.isEmpty()); - ASSERT_FALSE(personObj.isConst()); - - personObj.makeConst(); - ASSERT_TRUE(personObj.isConst()); - ASSERT_TRUE(updateAddress->hasSignature()); - - const RStatus& rStatus = (*updateAddress)(personObj)(); - - ASSERT_TRUE(rStatus); - EXPECT_TRUE(person::test_method_updateAddress_const(personObj.get())); - } - EXPECT_TRUE(person::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ConstMethodOverload, const_method_no_args_call_on_non_const_target) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classPerson = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(classPerson); - - optional updateAddress = classPerson->getMethod(person::str_updateAddress); - ASSERT_TRUE(updateAddress); - - string fnameStr = person::FIRST_NAME; - auto [status, personObj] = classPerson->instance(fnameStr); - - ASSERT_TRUE(status); - ASSERT_FALSE(personObj.isEmpty()); - ASSERT_FALSE(personObj.isConst()); - ASSERT_TRUE(updateAddress->hasSignature()); - - const RStatus& rStatus = (*updateAddress)(personObj)(); - - ASSERT_TRUE(rStatus); - EXPECT_TRUE(person::test_method_updateAddress(personObj.get())); - } - EXPECT_TRUE(person::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } -} \ No newline at end of file diff --git a/CxxReflectionTests/src/ConstructorTests.cpp b/CxxReflectionTests/src/ConstructorTests.cpp deleted file mode 100644 index e4210a32..00000000 --- a/CxxReflectionTests/src/ConstructorTests.cpp +++ /dev/null @@ -1,200 +0,0 @@ -#include - -#include "MyReflection.h" -#include "TestUtilsBook.h" -#include "TestUtilsDate.h" - -using namespace std; -using namespace rtl; -using namespace rtl::access; -using namespace test_utils; - -namespace rtl_tests -{ - TEST(RTLInterfaceCxxMirror, get_record_types_with_wrong_names) - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional badFunc = cxxMirror.getFunction(date::ns, "wrong_date_struct"); - EXPECT_FALSE(badFunc); - - optional badRec = cxxMirror.getRecord(date::ns, "wrong" + std::string(date::struct_)); - EXPECT_FALSE(badRec); - } - - - TEST(DynamicAllocConstructorDate, wrong_args) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classDate = cxxMirror.getRecord(date::ns, date::struct_); - ASSERT_TRUE(classDate); - - auto [status, instance] = classDate->instance("wrong", "args0", 10); - - ASSERT_TRUE(status == Error::SignatureMismatch); - ASSERT_TRUE(instance.isEmpty()); - } - EXPECT_TRUE(date::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(DynamicAllocConstructorDate, args_void) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classDate = cxxMirror.getRecord(date::ns, date::struct_); - ASSERT_TRUE(classDate); - - auto [status, instance] = classDate->instance(); - - ASSERT_TRUE(status); - ASSERT_FALSE(instance.isEmpty()); - EXPECT_TRUE(date::test_dynamic_alloc_instance_ctor<>(instance.get())); - } - EXPECT_TRUE(date::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(DynamicAllocConstructorDate, args_string) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classDate = cxxMirror.getRecord(date::ns, date::struct_); - ASSERT_TRUE(classDate); - - const string& dateStr = date::DATE_STR; - auto [status, instance] = classDate->instance(dateStr); - - ASSERT_TRUE(status); - ASSERT_FALSE(instance.isEmpty()); - EXPECT_TRUE(date::test_dynamic_alloc_instance_ctor(instance.get())); - } - EXPECT_TRUE(date::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(DynamicAllocConstructorDate, args_unsigned_unsigned_unsigned) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classDate = cxxMirror.getRecord(date::ns, date::struct_); - ASSERT_TRUE(classDate); - - auto [status, instance] = classDate->instance(date::DAY, date::MONTH, date::YEAR); - - ASSERT_TRUE(status); - ASSERT_FALSE(instance.isEmpty()); - - const bool isPassed = date::test_dynamic_alloc_instance_ctor(instance.get()); - EXPECT_TRUE(isPassed); - } - EXPECT_TRUE(date::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(DestructorDate, non_virtual) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classDate = cxxMirror.getRecord(date::ns, date::struct_); - ASSERT_TRUE(classDate); - - auto [status, instance] = classDate->instance(); - - ASSERT_TRUE(status); - ASSERT_FALSE(instance.isEmpty()); - EXPECT_TRUE(date::test_dynamic_alloc_instance_ctor<>(instance.get())); - } - EXPECT_TRUE(date::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(DynamicAllocConstructorBook, wrong_args) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classBook = cxxMirror.getRecord(book::class_); - ASSERT_TRUE(classBook); - - auto [status, instance] = classBook->instance(19.0, 87.5); - - ASSERT_TRUE(status == Error::SignatureMismatch); - ASSERT_TRUE(instance.isEmpty()); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(DynamicAllocConstructorBook, args_default) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classBook = cxxMirror.getRecord(book::class_); - ASSERT_TRUE(classBook); - - auto [status, instance] = classBook->instance(); - - ASSERT_TRUE(status); - ASSERT_FALSE(instance.isEmpty()); - EXPECT_TRUE(book::test_dynamic_alloc_instance_ctor(instance.get())); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(DynamicAllocConstructorBook, args_double_string) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classBook = cxxMirror.getRecord(book::class_); - ASSERT_TRUE(classBook); - - double price = book::PRICE; - string title = book::TITLE; - auto [status, instance] = classBook->instance(price, title); - - ASSERT_TRUE(status); - ASSERT_FALSE(instance.isEmpty()); - - const bool isPassed = book::test_dynamic_alloc_instance_ctor(instance.get()); - EXPECT_TRUE(isPassed); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(DestructorBook, non_virtual) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classBook = cxxMirror.getRecord(book::class_); - ASSERT_TRUE(classBook); - - auto [status, instance] = classBook->instance(); - - ASSERT_TRUE(status); - ASSERT_FALSE(instance.isEmpty()); - EXPECT_TRUE(book::test_dynamic_alloc_instance_ctor(instance.get())); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } -} \ No newline at end of file diff --git a/CxxReflectionTests/src/CopyConstructorTests.cpp b/CxxReflectionTests/src/CopyConstructorTests.cpp deleted file mode 100644 index 9f867a04..00000000 --- a/CxxReflectionTests/src/CopyConstructorTests.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include - -#include "MyReflection.h" -#include "TestUtilsBook.h" -#include "TestUtilsPerson.h" - -using namespace std; -using namespace rtl; -using namespace rtl::access; -using namespace test_utils; - -namespace rtl_tests -{ - - TEST(CopyConstructor, call_copy_ctor_of_PERSON_with_BOOK_instance) - { - { - optional classPerson = MyReflection::instance().getRecord(person::class_); - ASSERT_TRUE(classPerson); - - optional classBook = MyReflection::instance().getRecord(book::class_); - ASSERT_TRUE(classBook); - - auto [status, bookObj] = classBook->instance(); - ASSERT_TRUE(status); - ASSERT_FALSE(bookObj.isEmpty()); - - auto [retStatus, badObj] = classPerson->clone(bookObj); - - ASSERT_TRUE(retStatus == Error::InstanceTypeMismatch); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(CopyConstructor, copy_ctor_arg_const_ref___src_instance_non_const) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classBook = cxxMirror.getRecord(book::class_); - ASSERT_TRUE(classBook); - - optional setAuthor = classBook->getMethod(book::str_setAuthor); - ASSERT_TRUE(setAuthor); - - optional setDecription = classBook->getMethod(book::str_setDescription); - ASSERT_TRUE(setDecription); - - double price = book::PRICE; - string title = book::TITLE; - string author = book::AUTHOR; - string description = book::DESCRIPTION; - - auto [status, srcObj] = classBook->instance(price, title); - ASSERT_TRUE(status); - ASSERT_FALSE(srcObj.isEmpty()); - - (*setAuthor)(srcObj)(author); - (*setDecription)(srcObj)(description); - - auto [ret, copyObj] = classBook->clone(srcObj); - ASSERT_TRUE(ret); - ASSERT_FALSE(copyObj.isEmpty()); - - const bool isPassed = book::test_unique_copy_ctor_const_ref(copyObj.get()); - EXPECT_TRUE(isPassed); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(CopyConstructor, copy_ctor_arg_const_ref___src_instance_const) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classBook = cxxMirror.getRecord(book::class_); - ASSERT_TRUE(classBook); - - optional setAuthor = classBook->getMethod(book::str_setAuthor); - ASSERT_TRUE(setAuthor); - - optional setDecription = classBook->getMethod(book::str_setDescription); - ASSERT_TRUE(setDecription); - - double price = book::PRICE; - string title = book::TITLE; - string author = book::AUTHOR; - string description = book::DESCRIPTION; - - auto [status, srcObj] = classBook->instance(price, title); - ASSERT_TRUE(status); - ASSERT_FALSE(srcObj.isEmpty()); - - (*setAuthor)(srcObj)(author); - (*setDecription)(srcObj)(description); - - //make this instance const. - srcObj.makeConst(); - - auto [ret, copyObj] = classBook->clone(srcObj); - ASSERT_TRUE(ret); - ASSERT_FALSE(copyObj.isEmpty()); - - const bool isPassed = book::test_unique_copy_ctor_const_ref(copyObj.get()); - EXPECT_TRUE(isPassed); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(CopyConstructor, copy_ctor_arg_const_ref_overload___src_instance_const) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classPerson = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(classPerson); - - auto [status, srcObj] = classPerson->instance(); - ASSERT_TRUE(status); - ASSERT_FALSE(srcObj.isEmpty()); - - srcObj.makeConst(); - - auto [ret, copyObj] = classPerson->clone(srcObj); - ASSERT_TRUE(ret); - ASSERT_FALSE(copyObj.isEmpty()); - - const bool isPassed = person::test_copy_constructor_overload_src_const_obj(copyObj.get()); - EXPECT_TRUE(isPassed); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(CopyConstructor, copy_ctor_arg_non_const_ref_overload___src_instance_non_const) - { - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classPerson = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(classPerson); - - auto [status, srcObj] = classPerson->instance(); - ASSERT_TRUE(status); - ASSERT_FALSE(srcObj.isEmpty()); - - auto [ret, copyObj] = classPerson->clone(srcObj); - ASSERT_TRUE(ret); - ASSERT_FALSE(copyObj.isEmpty()); - - const bool isPassed = person::test_copy_constructor_overload_src_non_const_obj(copyObj.get()); - EXPECT_TRUE(isPassed); - } - EXPECT_TRUE(book::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } -} \ No newline at end of file diff --git a/CxxReflectionTests/src/NameSpaceGlobalsTests.cpp b/CxxReflectionTests/src/NameSpaceGlobalsTests.cpp deleted file mode 100644 index 05019476..00000000 --- a/CxxReflectionTests/src/NameSpaceGlobalsTests.cpp +++ /dev/null @@ -1,149 +0,0 @@ - -#include -#include - -#include "MyReflection.h" -#include "TestUtilsGlobals.h" - -using namespace std; -using namespace test_utils; -using namespace rtl::access; - -namespace rtl_tests -{ - TEST(RTLInterfaceCxxMirror, get_global_functions_with_wrong_names) - { - CxxMirror& cxxMirror = MyReflection::instance(); - { - optional badFunc = cxxMirror.getFunction("wrong_namespace", "wrong_function"); - EXPECT_FALSE(badFunc); - } { - optional badFunc = cxxMirror.getFunction(str_complex, "wrong_function"); - EXPECT_FALSE(badFunc); - } { - optional badFunc = cxxMirror.getFunction("wrong_getComplexNumAsString"); - EXPECT_FALSE(badFunc); - } - } - - - TEST(FunctionInNameSpace, get_namespace_function_types) - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional setReal = cxxMirror.getFunction(str_complex, str_setReal); - ASSERT_TRUE(setReal); - - optional setImaginary = cxxMirror.getFunction(str_complex, str_setImaginary); - ASSERT_TRUE(setImaginary); - - EXPECT_TRUE(setReal->getNamespace() == str_complex); - EXPECT_TRUE(setReal->getFunctionName() == str_setReal); - EXPECT_TRUE(setImaginary->getNamespace() == str_complex); - EXPECT_TRUE(setImaginary->getFunctionName() == str_setImaginary); - } - - - TEST(FunctionInNameSpace, namespace_function_execute_return) - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional getMagnitude = cxxMirror.getFunction(str_complex, str_getMagnitude); - ASSERT_TRUE(getMagnitude); - - optional setReal = cxxMirror.getFunction(str_complex, str_setReal); - ASSERT_TRUE(setReal); - - optional setImaginary = cxxMirror.getFunction(str_complex, str_setImaginary); - ASSERT_TRUE(setImaginary); - - EXPECT_TRUE(setReal->hasSignature()); - (*setReal)(g_real); - - EXPECT_TRUE(setImaginary->hasSignature()); - (*setImaginary)(g_imaginary); - - EXPECT_TRUE(getMagnitude->hasSignature()); - - RStatus status = (*getMagnitude)(); - - ASSERT_TRUE(status); - ASSERT_TRUE(status.getReturn().has_value()); - ASSERT_TRUE(status.isOfType()); - - double retVal = std::any_cast(status.getReturn()); - double magnitude = abs(complex(g_real, g_imaginary)); - EXPECT_DOUBLE_EQ(magnitude, retVal); - } - - - TEST(FunctionInNameSpace, execute_with_wrong_signature) - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional setReal = cxxMirror.getFunction(str_complex, str_setReal); - ASSERT_TRUE(setReal); - EXPECT_TRUE(setReal->hasSignature()); - - EXPECT_FALSE(setReal->hasSignature()); - - //different syntax, other than (*setReal)(float(g_real)) - RStatus status = setReal->call(float(g_real)); - - ASSERT_FALSE(status); - ASSERT_FALSE(status.getReturn().has_value()); - } - - - TEST(GlobalFunction, get_function_execute_return) - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional getComplexNumAsString = cxxMirror.getFunction(str_getComplexNumAsString); - ASSERT_TRUE(getComplexNumAsString); - - RStatus status = (*getComplexNumAsString)(); - - ASSERT_TRUE(status); - ASSERT_TRUE(status.getReturn().has_value()); - ASSERT_TRUE(status.isOfType()); - - string retVal = std::any_cast(status.getReturn()); - string comlexNumStr = to_string(g_real) + "i" + to_string(g_imaginary); - EXPECT_TRUE(comlexNumStr == retVal); - } - - - TEST(GlobalFunction, overloaded_function_execute_return) - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional reverseString = cxxMirror.getFunction(str_reverseString); - ASSERT_TRUE(reverseString); - { - RStatus status = (*reverseString)(string(STRA)); - ASSERT_TRUE(status); - ASSERT_TRUE(status.getReturn().has_value()); - ASSERT_TRUE(status.isOfType()); - - string retVal = std::any_cast(status.getReturn()); - EXPECT_TRUE(retVal == STRA_REVERSE); - } { - RStatus status = reverseString->call(string(STRB)); - ASSERT_TRUE(status); - ASSERT_TRUE(status.getReturn().has_value()); - ASSERT_TRUE(status.isOfType()); - - string retVal = std::any_cast(status.getReturn()); - EXPECT_TRUE(retVal == STRB_REVERSE); - } { - RStatus status = (*reverseString)(); - ASSERT_TRUE(status); - ASSERT_TRUE(status.getReturn().has_value()); - ASSERT_TRUE(status.isOfType()); - - string retVal = std::any_cast(status.getReturn()); - EXPECT_TRUE(retVal == REV_STR_VOID_RET); - } - } -} \ No newline at end of file diff --git a/CxxReflectionTests/src/ReflectedCallStatusErrTests.cpp b/CxxReflectionTests/src/ReflectedCallStatusErrTests.cpp deleted file mode 100644 index 0b72ef1d..00000000 --- a/CxxReflectionTests/src/ReflectedCallStatusErrTests.cpp +++ /dev/null @@ -1,169 +0,0 @@ - -#include - -#include "MyReflection.h" -#include "TestUtilsBook.h" -#include "TestUtilsDate.h" -#include "TestUtilsPerson.h" - -using namespace std; -using namespace rtl; -using namespace rtl::access; -using namespace test_utils; - -namespace rtl_tests -{ - TEST(ReflectedCallStatusError, unregistered_constructor___error_ConstructorNotFound) - { - optional classLibrary = MyReflection::instance().getRecord(library::class_); - ASSERT_TRUE(classLibrary); - - auto [status, instance] = classLibrary->instance(); - - ASSERT_TRUE(status == Error::ConstructorNotFound); - ASSERT_TRUE(instance.isEmpty()); - } - - - TEST(ReflectedCallStatusError, unregistered_constructor___error_CopyConstructorNotFound) - { - { - optional classCalender = MyReflection::instance().getRecord(calender::ns, calender::struct_); - ASSERT_TRUE(classCalender); - - auto [ret, srcObj] = classCalender->instance(); - ASSERT_TRUE(ret); - ASSERT_FALSE(srcObj.isEmpty()); - - auto [status, instance] = classCalender->clone(srcObj); - - ASSERT_TRUE(status == Error::CopyConstructorNotFound); - ASSERT_TRUE(instance.isEmpty()); - } - EXPECT_TRUE(calender::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ReflectedCallStatusError, static_method_call_wrong_args___error_SignatureMismatch) - { - optional classPerson = MyReflection::instance().getRecord(person::class_); - ASSERT_TRUE(classPerson); - - optional getProfile = classPerson->getMethod(person::str_getProfile); - ASSERT_TRUE(getProfile); - ASSERT_TRUE(getProfile->hasSignature()); - - const RStatus& status = getProfile->on().call(std::string()); - - ASSERT_TRUE(status == Error::SignatureMismatch); - } - - - TEST(ReflectedCallStatusError, copy_ctor_on_empty_instance___error_EmptyInstance) - { - optional classLibrary = MyReflection::instance().getRecord(library::class_); - ASSERT_TRUE(classLibrary); - - auto [status, emptyObj] = classLibrary->instance(); - - ASSERT_TRUE(status == Error::ConstructorNotFound); - ASSERT_TRUE(emptyObj.isEmpty()); - - optional classPerson = MyReflection::instance().getRecord(person::class_); - ASSERT_TRUE(classPerson); - - auto [retStatus, personObj] = classPerson->clone(emptyObj); - - ASSERT_TRUE(retStatus == Error::EmptyInstance); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ReflectedCallStatusError, method_call_on_empty_instance___error_EmptyInstance) - { - optional classLibrary = MyReflection::instance().getRecord(library::class_); - ASSERT_TRUE(classLibrary); - - auto [ret, emptyObj] = classLibrary->instance(); - - ASSERT_TRUE(ret == Error::ConstructorNotFound); - ASSERT_TRUE(emptyObj.isEmpty()); - - optional classBook = MyReflection::instance().getRecord(book::class_); - ASSERT_TRUE(classBook); - - RStatus retStatus = classBook->getMethod(book::str_getPublishedOn)->on(emptyObj).call(); - ASSERT_TRUE(retStatus == Error::EmptyInstance); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ReflectedCallStatusError, unregistered_constructor___error_ConstCopyConstructorNotFound) - { - { - optional classDate = MyReflection::instance().getRecord(date::ns, date::struct_); - ASSERT_TRUE(classDate); - - auto [ret, srcObj] = classDate->instance(); - ASSERT_TRUE(ret); - ASSERT_FALSE(srcObj.isEmpty()); - - srcObj.makeConst(); - - auto [status, instance] = classDate->clone(srcObj); - - ASSERT_TRUE(status == Error::ConstCopyConstructorNotFound); - ASSERT_TRUE(instance.isEmpty()); - } - EXPECT_TRUE(date::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ReflectedCallStatusError, method_on_wrong_instance___error_InstanceTypeMismatch) - { - { - optional classPerson = MyReflection::instance().getRecord(person::class_); - ASSERT_TRUE(classPerson); - - optional classBook = MyReflection::instance().getRecord(book::class_); - ASSERT_TRUE(classBook); - - auto [status, personObj] = classPerson->instance(); - ASSERT_TRUE(status); - ASSERT_FALSE(personObj.isEmpty()); - - optional getPublishedOn = classBook->getMethod(book::str_getPublishedOn); - ASSERT_TRUE(getPublishedOn); - - RStatus retStatus = getPublishedOn->on(personObj).call(); - ASSERT_TRUE(retStatus == Error::InstanceTypeMismatch); - } - EXPECT_TRUE(person::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } - - - TEST(ReflectedCallStatusError, non_const_method_on_const_Instance__error_InstanceConstMismatch) - { - { - optional classBook = MyReflection::instance().getRecord(book::class_); - ASSERT_TRUE(classBook); - - auto [status, bookObj] = classBook->instance(); - ASSERT_TRUE(status); - ASSERT_FALSE(bookObj.isEmpty()); - - optional getPublishedOn = classBook->getMethod(book::str_getPublishedOn); - ASSERT_TRUE(getPublishedOn); - - bookObj.makeConst(); - RStatus retStatus = getPublishedOn->on(bookObj).call(); - - ASSERT_TRUE(retStatus == Error::InstanceConstMismatch); - } - EXPECT_TRUE(person::assert_zero_instance_count()); - EXPECT_TRUE(Instance::getInstanceCount() == 0); - } -} \ No newline at end of file diff --git a/CxxReflectionTests/src/StaticMethodTests.cpp b/CxxReflectionTests/src/StaticMethodTests.cpp deleted file mode 100644 index 083b0115..00000000 --- a/CxxReflectionTests/src/StaticMethodTests.cpp +++ /dev/null @@ -1,135 +0,0 @@ - -#include - -#include "MyReflection.h" -#include "TestUtilsPerson.h" - -using namespace std; -using namespace rtl::access; -using namespace test_utils; - -namespace rtl_tests -{ - TEST(StaticMethods, unique_method_call) - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classPerson = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(classPerson); - - optional getDefaults = classPerson->getMethod(person::str_getDefaults); - ASSERT_TRUE(getDefaults); - ASSERT_TRUE(getDefaults->hasSignature()); - - const RStatus& status = (*getDefaults)()(); - ASSERT_TRUE(status); - ASSERT_TRUE(status.getReturn().has_value()); - ASSERT_TRUE(status.isOfType()); - - const string& retStr = any_cast(status.getReturn()); - EXPECT_EQ(retStr, person::get_str_returned_on_call_getDefaults()); - } - - - TEST(StaticMethods, overload_method_void_call) - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classPerson = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(classPerson); - - optional getProfile = classPerson->getMethod(person::str_getProfile); - ASSERT_TRUE(getProfile); - ASSERT_TRUE(getProfile->hasSignature()); - - const RStatus& status = getProfile->on().call(); - ASSERT_TRUE(status); - ASSERT_TRUE(status.getReturn().has_value()); - ASSERT_TRUE(status.isOfType()); - - const string& retStr = any_cast(status.getReturn()); - EXPECT_EQ(retStr, person::get_str_returned_on_call_getProfile()); - } - - - TEST(StaticMethods, overload_method_args_bool_call) - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classPerson = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(classPerson); - - optional getProfile = classPerson->getMethod(person::str_getProfile); - ASSERT_TRUE(getProfile); - ASSERT_TRUE(getProfile->hasSignature()); - { - const RStatus& status = (*getProfile)()(true); - ASSERT_TRUE(status); - ASSERT_TRUE(status.getReturn().has_value()); - ASSERT_TRUE(status.isOfType()); - - const string& retStr = any_cast(status.getReturn()); - EXPECT_EQ(retStr, person::get_str_returned_on_call_getProfile(true)); - } { - //different syntax of calling. - const RStatus& status = getProfile->on().call(false); - - ASSERT_TRUE(status); - ASSERT_TRUE(status.getReturn().has_value()); - ASSERT_TRUE(status.isOfType()); - - const string& retStr = any_cast(status.getReturn()); - EXPECT_EQ(retStr, person::get_str_returned_on_call_getProfile(false)); - } - } - - - TEST(StaticMethods, overload_method_args_string_size_t_call) - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional recOpt = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(recOpt.has_value()); - - const Record& classPerson = recOpt.value(); - optional methOpt = classPerson.getMethod(person::str_getProfile); - ASSERT_TRUE(methOpt.has_value()); - - const Method& getProfile = methOpt.value(); - const bool& signValid = getProfile.hasSignature(); - ASSERT_TRUE(signValid); - - const RStatus& status = getProfile.on().call(string(person::OCCUPATION), person::AGE); - - ASSERT_TRUE(status); - ASSERT_TRUE(status.getReturn().has_value()); - ASSERT_TRUE(status.isOfType()); - - const string& retStr = any_cast(status.getReturn()); - const string& checkStr = person::get_str_returned_on_call_getProfile(); - - EXPECT_EQ(retStr, checkStr); - } - - - TEST(StaticMethods, static_method_call_on_target_instance) - { - CxxMirror& cxxMirror = MyReflection::instance(); - - optional classPerson = cxxMirror.getRecord(person::class_); - ASSERT_TRUE(classPerson); - - optional getDefaults = classPerson->getMethod(person::str_getDefaults); - ASSERT_TRUE(getDefaults); - ASSERT_TRUE(getDefaults->hasSignature()); - - auto [isSuccess, personObj] = classPerson->instance(); - - ASSERT_TRUE(isSuccess); - ASSERT_FALSE(personObj.isEmpty()); - - //TODO: handle this test case with appropriate error or make successful call as its valid to call static method on objects. - const RStatus& status = (*getDefaults)(personObj)(); - ASSERT_TRUE(status == rtl::Error::InstanceTypeMismatch); - } -} \ No newline at end of file diff --git a/CxxTestProject/inc/Date.h b/CxxTestProject/inc/Date.h deleted file mode 100644 index e0dd018b..00000000 --- a/CxxTestProject/inc/Date.h +++ /dev/null @@ -1,43 +0,0 @@ - -#pragma once - -#include - -namespace nsdate -{ - struct Date - { - Date(); - Date(Date& pOther); - Date(const std::string& pDateStr); - Date(unsigned dd, unsigned mm, unsigned yy); - - const bool operator==(const Date& pOther) const; - - ~Date(); - - static unsigned instanceCount(); - - std::string getAsString(); - - private: - - unsigned m_day; - unsigned m_month; - unsigned m_year; - static unsigned m_instanceCount; - }; - - - //for testing 'copy constructor found' - struct Calender - { - Calender(); - ~Calender(); - - static unsigned instanceCount(); - - private: - static unsigned m_instanceCount; - }; -} \ No newline at end of file diff --git a/CxxTestProject/src/Date.cpp b/CxxTestProject/src/Date.cpp deleted file mode 100644 index d7ebe59e..00000000 --- a/CxxTestProject/src/Date.cpp +++ /dev/null @@ -1,93 +0,0 @@ - -#include -#include -#include "Date.h" - -using namespace std; - -static int g_dateObjCount = 0; -static int g_calenderObjCount = 0; - -namespace nsdate -{ - unsigned int Date::m_instanceCount = 0; - unsigned int Calender::m_instanceCount = 0; - - Calender::Calender() - { - m_instanceCount++; - } - - Calender::~Calender() - { - m_instanceCount--; - } - - unsigned Calender::instanceCount() - { - return g_calenderObjCount; - } - - - Date::~Date() { - m_instanceCount--; - } - - unsigned Date::instanceCount() - { - return m_instanceCount; - } - - - std::string Date::getAsString() - { - return (to_string(m_day) + "/" + to_string(m_month) + "/" + to_string(m_year)); - } - - - Date::Date() - : m_day(1) - , m_month(1) - , m_year(2000) { - m_instanceCount++; - } - - Date::Date(Date& pOther) - : m_day(pOther.m_day) - , m_month(pOther.m_month) - , m_year(pOther.m_year) { - m_instanceCount++; - } - - Date::Date(unsigned dd, unsigned mm, unsigned yy) - : m_day(dd) - , m_month(mm) - , m_year(yy) { - m_instanceCount++; - } - - const bool Date::operator==(const Date& pOther) const - { - return (m_day == pOther.m_day && m_month == pOther.m_month && m_year == pOther.m_year); - } - - Date::Date(const string& pDateStr) - { - m_instanceCount++; - string strBuf; - vector date; - for (size_t i = 0; i < pDateStr.length(); i++) - { - if (pDateStr.at(i) == '/') { - date.push_back(strBuf); - strBuf.clear(); - } - else { - strBuf.push_back(pDateStr.at(i)); - } - } - m_day = stoi(date[0]); - m_month = stoi(date[1]); - m_year = stoi(strBuf); - } -} \ No newline at end of file diff --git a/CxxTestProject/CMakeLists.txt b/CxxTestProps/CMakeLists.txt similarity index 80% rename from CxxTestProject/CMakeLists.txt rename to CxxTestProps/CMakeLists.txt index 21ddfcfa..4d9a99a0 100644 --- a/CxxTestProject/CMakeLists.txt +++ b/CxxTestProps/CMakeLists.txt @@ -1,10 +1,10 @@ -# CMakeLists.txt for CxxTestProject +# CMakeLists.txt for CxxTestProps # Set the minimum required CMake version cmake_minimum_required(VERSION 3.20) # Set the project name -project(CxxTestProject) +project(CxxTestProps) set(CMAKE_CXX_STANDARD 20) diff --git a/CxxTestProps/inc/Animal.h b/CxxTestProps/inc/Animal.h new file mode 100644 index 00000000..36db6fd1 --- /dev/null +++ b/CxxTestProps/inc/Animal.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +class Animal +{ + std::string m_name; + std::string m_familyName; + + static std::string m_zooKeeper; + static unsigned m_instanceCount; + +public: + + Animal(); + ~Animal(); + Animal(const std::string& pFamilyName); + + Animal(Animal&& pOther) noexcept; + + Animal& operator=(Animal&& pOther) noexcept; + + Animal(const Animal& pOther); + + Animal& operator=(const Animal& pOther); + + const bool operator==(const Animal& pOther) const; + + void setFamilyName(const std::string pName); + + std::string getFamilyName() const; + + void setAnimalName(std::string& pName); + + void setAnimalName(std::string&& pName); + + void setAnimalName(const std::string& pName); + + static std::string updateZooKeeper(std::string& pZooKeeper); + + static std::string updateZooKeeper(std::string&& pZooKeeper); + + static std::string updateZooKeeper(const std::string& pZooKeeper); + + static unsigned getInstanceCount(); +}; \ No newline at end of file diff --git a/CxxTestProject/inc/Book.h b/CxxTestProps/inc/Book.h similarity index 65% rename from CxxTestProject/inc/Book.h rename to CxxTestProps/inc/Book.h index f743a226..8c8c1ce3 100644 --- a/CxxTestProject/inc/Book.h +++ b/CxxTestProps/inc/Book.h @@ -1,3 +1,4 @@ +#pragma once #include #include "Date.h" @@ -12,35 +13,31 @@ class Book std::string m_author; std::string m_description; - static unsigned m_instanceCount; + static int m_instanceCount; public: Book(); Book(const Book& pOther); + Book(Book&& pOther) noexcept; Book(double pPrice, std::string pTitle); ~Book(); + std::string getTitle() const; std::string getPublishedOn(); - void setAuthor(std::string pAuthor); void setDescription(std::string pDesc); + void addCopyrightTag(const std::string pPubInfo); void updateBookInfo(); void updateBookInfo(const char* pTitle, double pPrice, std::string pAuthor); void updateBookInfo(std::string pAuthor, double pPrice, const char* pTitle); - const bool operator==(const Book& pOther) const; - - static unsigned getInstanceCount(); -}; + void addPreface(const std::string pAcknowledgements, const std::string& pPreface); + Book& operator=(const Book& pOther) = default; + const bool operator==(const Book& pOther) const; -class Library -{ -public: - //for testing 'no constructor found' only. - Library() { } - static void addBook(Book pBook) { } -}; + static int getInstanceCount(); +}; \ No newline at end of file diff --git a/CxxTestProject/inc/Complex.h b/CxxTestProps/inc/Complex.h similarity index 99% rename from CxxTestProject/inc/Complex.h rename to CxxTestProps/inc/Complex.h index d3065400..40441aa2 100644 --- a/CxxTestProject/inc/Complex.h +++ b/CxxTestProps/inc/Complex.h @@ -17,5 +17,4 @@ namespace complex void setReal(double pNum); void setImaginary(double pNum); -} - +} \ No newline at end of file diff --git a/CxxTestProps/inc/Date.h b/CxxTestProps/inc/Date.h new file mode 100644 index 00000000..343bc927 --- /dev/null +++ b/CxxTestProps/inc/Date.h @@ -0,0 +1,105 @@ + +#pragma once + +#include +#include + +namespace nsdate +{ + struct Date + { + Date(); + Date(const Date& pOther); + Date(const std::string& pDateStr); + Date(unsigned dd, unsigned mm, unsigned yy); + Date(Date&&) noexcept; + + Date& operator=(Date&&) = default; + Date& operator=(const Date&) = default; + + const bool operator==(const Date& pOther) const; + + ~Date(); + + static std::size_t instanceCount(); + + std::string getAsString() const; + + void updateDate(std::string pDateStr); + + private: + + unsigned m_day; + unsigned m_month; + unsigned m_year; + static std::size_t m_instanceCount; + }; + + + struct Event; + + struct Calender + { + Calender(); + ~Calender(); + Calender(Calender&&) noexcept; + Calender(const Calender&); + + Calender& operator=(Calender&&) = delete; + Calender& operator=(const Calender&) = delete; + + Date& getTheDate(); + Date& getSavedDate(); + + const Event& getTheEvent(); + const Event& getSavedEvent(); + + static void resetMoveOpsCounter(); + static std::size_t instanceCount(); + static std::size_t getMoveOpsCount(); + + static Calender create(); + + private: + + std::shared_ptr m_theEvent; + + std::unique_ptr m_savedEvent; + + static std::size_t m_instanceCount; + + static std::size_t m_moveOpsCount; + }; + + + struct Event + { + ~Event(); + + Event(Event&&) = delete; + + static std::size_t instanceCount(); + + const Date& getEventDate(); + + void reset(); + + private: + + Event(); + Event(const Event& pOther); + + std::unique_ptr m_date; + + static std::size_t m_instanceCount; + + static Event* create(); + static Event* createCopy(const Event& pOther); + + //friends :) + friend Calender; + + Event& operator=(Event&&) = delete; + Event& operator=(const Event&) = delete; + }; +} diff --git a/CxxTestProps/inc/Library.h b/CxxTestProps/inc/Library.h new file mode 100644 index 00000000..4e4e71dc --- /dev/null +++ b/CxxTestProps/inc/Library.h @@ -0,0 +1,25 @@ + +#pragma once +#include + +class Book; + +class Library +{ + static std::unordered_map m_booksByTitle; + +public: + + Library(); + ~Library(); + + Library(const Library&) = delete; + + static int getBooksCount(); + + static std::size_t getInstanceCount(); + + static void addBook(const Book& pBook); + + static Book getBookByTitle(const std::string& pTitle); +}; \ No newline at end of file diff --git a/CxxTestProject/inc/Person.h b/CxxTestProps/inc/Person.h similarity index 80% rename from CxxTestProject/inc/Person.h rename to CxxTestProps/inc/Person.h index b429b6d1..a534b5f8 100644 --- a/CxxTestProject/inc/Person.h +++ b/CxxTestProps/inc/Person.h @@ -10,19 +10,17 @@ class Person public: - ~Person(); Person(); - Person(const std::string& pName); - - Person(Person& pOther); - + ~Person(); + Person(Person&&) noexcept; Person(const Person& pOther); + Person(const std::string& pName); void updateAddress(); void updateAddress() const; - std::string getFirstName() const; + std::string getFirstName(); void updateAddress(std::string pAddress); @@ -40,5 +38,11 @@ class Person static std::string getProfile(std::string pOccupation, std::size_t pAge); + static const Person createConst(); + + static const Person* createPtr(); + + static void deletePtr(const Person* ptr); + static unsigned getInstanceCount(); }; \ No newline at end of file diff --git a/CxxTestProps/src/Animal.cpp b/CxxTestProps/src/Animal.cpp new file mode 100644 index 00000000..f85b8e0d --- /dev/null +++ b/CxxTestProps/src/Animal.cpp @@ -0,0 +1,139 @@ + +#include "Animal.h" + +unsigned Animal::m_instanceCount = 0; +std::string Animal::m_zooKeeper = "__no_zookeeper.."; + + +Animal::Animal() + : m_name("__no_name..") + , m_familyName("__no_family_ :( ") +{ + m_instanceCount++; +} + + +Animal::~Animal() +{ + m_instanceCount--; +} + + +Animal::Animal(const std::string& pFamilyName) + : m_name("__no_name..") + , m_familyName(pFamilyName) +{ + m_instanceCount++; +} + + +Animal::Animal(const Animal& pOther) + : m_name(pOther.m_name) + , m_familyName(pOther.m_familyName) +{ + m_instanceCount++; +} + + +Animal& Animal::operator=(const Animal& pOther) +{ + if (this == &pOther) + return *this; + m_name = pOther.m_name; + m_familyName = pOther.m_familyName; + return *this; +} + + +Animal::Animal(Animal&& pOther) noexcept + : m_name(pOther.m_name) + , m_familyName(pOther.m_familyName) +{ + m_instanceCount++; + pOther.m_name.clear(); + pOther.m_familyName.clear(); +} + + +Animal& Animal::operator=(Animal&& pOther) noexcept +{ + if (this == &pOther) + return *this; + + m_name = pOther.m_name + "__move_assignment"; + m_familyName = pOther.m_familyName + "__move_assignment"; + + pOther.m_name.clear(); + pOther.m_familyName.clear(); + return *this; +} + + +void Animal::setAnimalName(std::string& pName) +{ + m_name = pName + "__args_non_const_lvalue_ref..."; +} + + +void Animal::setFamilyName(const std::string pName) +{ + m_familyName = pName; +} + + +std::string Animal::getFamilyName() const +{ + return m_familyName; +} + + +void Animal::setAnimalName(std::string&& pName) +{ + m_name = pName + "__args_rvalue_ref..."; +} + + +unsigned Animal::getInstanceCount() +{ + return m_instanceCount; +} + + +void Animal::setAnimalName(const std::string& pName) +{ + m_name = pName + "__args_const_lvalue_ref..."; +} + + +std::string Animal::updateZooKeeper(std::string& pZooKeeper) +{ + m_zooKeeper = pZooKeeper + "__args_non_const_lvalue_ref..."; + return m_zooKeeper; +} + + +std::string Animal::updateZooKeeper(std::string&& pZooKeeper) +{ + m_zooKeeper = pZooKeeper + "__args_rvalue_ref..."; + return m_zooKeeper; +} + + +std::string Animal::updateZooKeeper(const std::string& pZooKeeper) +{ + m_zooKeeper = pZooKeeper + "__args_const_lvalue_ref..."; + return m_zooKeeper; +} + + +const bool Animal::operator==(const Animal& pOther) const +{ + if (this == &pOther) + return true; + + if (m_name != pOther.m_name || m_familyName != pOther.m_familyName) { + return false; + } + + return true; +} \ No newline at end of file diff --git a/CxxTestProject/src/Book.cpp b/CxxTestProps/src/Book.cpp similarity index 67% rename from CxxTestProject/src/Book.cpp rename to CxxTestProps/src/Book.cpp index ff2998d4..e9a0b4e9 100644 --- a/CxxTestProject/src/Book.cpp +++ b/CxxTestProps/src/Book.cpp @@ -1,10 +1,9 @@ #include "Book.h" -using namespace std; using namespace nsdate; -unsigned Book::m_instanceCount = 0; +int Book::m_instanceCount = 0; Book::~Book() { m_instanceCount--; @@ -28,8 +27,17 @@ Book::Book(const Book& pOther) m_instanceCount++; } +Book::Book(Book&& pOther) noexcept + : m_price(pOther.m_price) + , m_title(pOther.m_title) + , m_date(pOther.m_date) + , m_author(pOther.m_author) + , m_description(pOther.m_description) { + m_instanceCount++; +} + -Book::Book(double pPrice, string pTitle) +Book::Book(double pPrice, std::string pTitle) : m_price(pPrice) , m_title(pTitle) , m_author("no_author_ctor_double_string") @@ -39,7 +47,7 @@ Book::Book(double pPrice, string pTitle) } -void Book::setAuthor(string pAuthor) { +void Book::setAuthor(std::string pAuthor) { m_author = pAuthor; } @@ -49,6 +57,11 @@ void Book::setDescription(std::string pDesc) m_description = pDesc; } +void Book::addCopyrightTag(const std::string pPubInfo) +{ + m_description += pPubInfo; +} + const bool Book::operator==(const Book& pOther) const { return (m_price == pOther.m_price && m_author == pOther.m_author && m_date == pOther.m_date && @@ -56,13 +69,18 @@ const bool Book::operator==(const Book& pOther) const { } -string Book::getPublishedOn() { +std::string Book::getTitle() const +{ + return m_title; +} + +std::string Book::getPublishedOn() { return m_date.getAsString(); } -unsigned Book::getInstanceCount() { +int Book::getInstanceCount() { return m_instanceCount; } @@ -79,7 +97,7 @@ void Book::updateBookInfo(const char* pTitle, double pPrice, std::string pAuthor { m_price = pPrice; m_date = nsdate::Date(9, 10, 2020); - m_title = string(pTitle) + "[Discontinued]"; + m_title = std::string(pTitle) + "[Discontinued]"; m_author = pAuthor + " (Retired)"; } @@ -88,6 +106,11 @@ void Book::updateBookInfo(std::string pAuthor, double pPrice, const char* pTitle { m_price = pPrice; m_date = nsdate::Date(6, 12, 1999); - m_title = string(pTitle) + "[BestSeller]"; + m_title = std::string(pTitle) + "[BestSeller]"; m_author = pAuthor + " (Independent)"; -} \ No newline at end of file +} + +void Book::addPreface(const std::string pAcknowledgements, const std::string& pPreface) +{ + m_description += pPreface + " " + pAcknowledgements; +} diff --git a/CxxTestProject/src/CMakeLists.txt b/CxxTestProps/src/CMakeLists.txt similarity index 73% rename from CxxTestProject/src/CMakeLists.txt rename to CxxTestProps/src/CMakeLists.txt index 1d1b6e60..56de8e88 100644 --- a/CxxTestProject/src/CMakeLists.txt +++ b/CxxTestProps/src/CMakeLists.txt @@ -4,17 +4,21 @@ set(LOCAL_SOURCES "${CMAKE_CURRENT_LIST_DIR}/Complex.cpp" "${CMAKE_CURRENT_LIST_DIR}/Date.cpp" "${CMAKE_CURRENT_LIST_DIR}/Person.cpp" + "${CMAKE_CURRENT_LIST_DIR}/Animal.cpp" + "${CMAKE_CURRENT_LIST_DIR}/Library.cpp" ) SET(LOCAL_HEADERS "${PROJECT_SOURCE_DIR}/inc/Book.h" "${PROJECT_SOURCE_DIR}/inc/Complex.h" "${PROJECT_SOURCE_DIR}/inc/Date.h" + "${PROJECT_SOURCE_DIR}/inc/Animal.h" "${PROJECT_SOURCE_DIR}/inc/Person.h" + "${PROJECT_SOURCE_DIR}/inc/Library.h" ) # Add any additional source files if needed -target_sources(CxxTestProject +target_sources(CxxTestProps PRIVATE "${LOCAL_HEADERS}" "${LOCAL_SOURCES}" diff --git a/CxxTestProject/src/Complex.cpp b/CxxTestProps/src/Complex.cpp similarity index 97% rename from CxxTestProject/src/Complex.cpp rename to CxxTestProps/src/Complex.cpp index 1f337752..9c9315e4 100644 --- a/CxxTestProject/src/Complex.cpp +++ b/CxxTestProps/src/Complex.cpp @@ -1,5 +1,6 @@ #include +#include #include "Complex.h" @@ -54,4 +55,4 @@ namespace complex std::string getComplexNumAsString() { return std::to_string(complex::g_realNumber) + "i" + (std::to_string(complex::g_imgNumber)); -} \ No newline at end of file +} diff --git a/CxxTestProps/src/Date.cpp b/CxxTestProps/src/Date.cpp new file mode 100644 index 00000000..a3b76538 --- /dev/null +++ b/CxxTestProps/src/Date.cpp @@ -0,0 +1,223 @@ +#include "Date.h" + +#include +#include "Date.h" + +using namespace std; + +namespace nsdate +{ + std::size_t Date::m_instanceCount = 0; + std::size_t Event::m_instanceCount = 0; + std::size_t Calender::m_instanceCount = 0; + std::size_t Calender::m_moveOpsCount = 0; + + Calender::~Calender() + { + m_instanceCount--; + } + + Calender::Calender() + : m_theEvent(std::shared_ptr(Event::create())) + , m_savedEvent(std::unique_ptr(Event::create())) + { + m_instanceCount++; + } + + Calender::Calender(const Calender& pOther) + : m_theEvent(pOther.m_theEvent) + , m_savedEvent(pOther.m_savedEvent ? std::unique_ptr(Event::createCopy(*pOther.m_savedEvent)) : nullptr) + { + m_instanceCount++; + } + + Calender::Calender(Calender&& pOther) noexcept + : m_theEvent(std::move(pOther.m_theEvent)) + , m_savedEvent(std::move(pOther.m_savedEvent)) + { + m_moveOpsCount++; + m_instanceCount++; + } + + Calender Calender::create() + { + return Calender(); + } + + const Event& Calender::getTheEvent() + { + return *m_theEvent; + } + + const Event& Calender::getSavedEvent() + { + return *m_savedEvent; + } + + Date& Calender::getTheDate() + { + return *(m_theEvent->m_date); + } + + Date& Calender::getSavedDate() + { + return *(m_savedEvent->m_date); + } + + std::size_t Calender::instanceCount() + { + return m_instanceCount; + } + + std::size_t Calender::getMoveOpsCount() + { + return m_moveOpsCount; + } + + void Calender::resetMoveOpsCounter() + { + m_moveOpsCount = 0; + } +} + +namespace nsdate +{ + Event::~Event() + { + m_instanceCount--; + } + + Event::Event() + : m_date(std::make_unique()) + { + m_instanceCount++; + } + + Event::Event(const Event& pOther) + : m_date(pOther.m_date ? std::make_unique(*pOther.m_date) : nullptr) + { + m_instanceCount++; + } + + const Date& Event::getEventDate() + { + return *m_date; + } + + + void Event::reset() + { + //does nothing yet. + } + + + std::size_t Event::instanceCount() + { + return m_instanceCount; + } + + Event* Event::create() + { + return new Event(); + } + + Event* Event::createCopy(const Event& pOther) + { + return new Event(pOther); + } +} + + +namespace nsdate +{ + Date::~Date() { + m_instanceCount--; + } + + std::size_t Date::instanceCount() + { + return m_instanceCount; + } + + std::string Date::getAsString() const + { + return (to_string(m_day) + "/" + to_string(m_month) + "/" + to_string(m_year)); + } + + + void Date::updateDate(std::string pDateStr) + { + string strBuf; + vector date; + for (size_t i = 0; i < pDateStr.length(); i++) + { + if (pDateStr[i] == '/') { + date.push_back(strBuf); + strBuf.clear(); + } + else { + strBuf.push_back(pDateStr[i]); + } + } + m_day = stoi(date[0]); + m_month = stoi(date[1]); + m_year = stoi(strBuf); + } + + Date::Date() + : m_day(1) + , m_month(1) + , m_year(2000) + { + m_instanceCount++; + } + + Date::Date(const Date& pOther) + : m_day(pOther.m_day) + , m_month(pOther.m_month) + , m_year(pOther.m_year) + { + m_instanceCount++; + } + + Date::Date(unsigned dd, unsigned mm, unsigned yy) + : m_day(dd) + , m_month(mm) + , m_year(yy) + { + m_instanceCount++; + } + + Date::Date(Date&& pOther) noexcept + : m_day(pOther.m_day) + , m_month(pOther.m_month) + , m_year(pOther.m_year) + { + m_instanceCount++; + } + + const bool Date::operator==(const Date& pOther) const + { + return (m_day == pOther.m_day && m_month == pOther.m_month && m_year == pOther.m_year); + } + + Date::Date(const string& pDateStr) + { + m_instanceCount++; + string strBuf; + vector date; + for (size_t i = 0; i < pDateStr.length(); i++) + { + if (pDateStr[i] == '/') { + date.push_back(strBuf); + strBuf.clear(); + } + else { + strBuf.push_back(pDateStr[i]); + } + } + m_day = stoi(date[0]); + m_month = stoi(date[1]); + m_year = stoi(strBuf); + } +} \ No newline at end of file diff --git a/CxxTestProps/src/Library.cpp b/CxxTestProps/src/Library.cpp new file mode 100644 index 00000000..a7d117d8 --- /dev/null +++ b/CxxTestProps/src/Library.cpp @@ -0,0 +1,37 @@ + +#include "Book.h" +#include "Library.h" + +std::size_t g_instanceCount = 0; + +std::unordered_map Library::m_booksByTitle; + +Library::Library() +{ + g_instanceCount++; +} + +Library::~Library() +{ + g_instanceCount--; +} + +std::size_t Library::getInstanceCount() +{ + return g_instanceCount; +} + +int Library::getBooksCount() +{ + return m_booksByTitle.size(); +} + +void Library::addBook(const Book& pBook) +{ + m_booksByTitle[pBook.getTitle()] = pBook; +} + +Book Library::getBookByTitle(const std::string& pTitle) +{ + return m_booksByTitle[pTitle]; +} \ No newline at end of file diff --git a/CxxTestProject/src/Person.cpp b/CxxTestProps/src/Person.cpp similarity index 86% rename from CxxTestProject/src/Person.cpp rename to CxxTestProps/src/Person.cpp index ab5ebefd..e074471d 100644 --- a/CxxTestProject/src/Person.cpp +++ b/CxxTestProps/src/Person.cpp @@ -1,5 +1,7 @@ + #include #include + #include "Person.h" static long g_instanceCount = 0; @@ -12,6 +14,16 @@ Person::~Person() } } +Person::Person(Person&& pOther) noexcept + : m_address(pOther.m_address) + , m_lastName(pOther.m_lastName) + , m_firstName(pOther.m_firstName) +{ + g_instanceCount++; + pOther.m_address.clear(); + pOther.m_lastName.clear(); +} + Person::Person() : m_address("182 st. Westoros, Dune.") , m_lastName("Doe") @@ -28,13 +40,6 @@ Person::Person(const std::string& pName) g_instanceCount++; } -Person::Person(Person& pOther) - : m_address(pOther.m_address + ".__Person::Person(Person&)") - , m_lastName(pOther.m_lastName + ".__Person::Person(Person&)") - , m_firstName(pOther.m_firstName + ".__Person::Person(Person&)") -{ - g_instanceCount++; -} Person::Person(const Person& pOther) : m_address(pOther.m_address + ".__Person::Person(const Person&)") @@ -62,7 +67,7 @@ void Person::updateAddress() const } -std::string Person::getFirstName() const +std::string Person::getFirstName() { return m_firstName; } @@ -95,6 +100,21 @@ std::string Person::getProfile(std::string pOccupation, std::size_t pAge) "\nAge: " + std::to_string(pAge) + "\n[__Person::getProfile(string, size_t)]"); } +const Person Person::createConst() +{ + return Person(); +} + +const Person* Person::createPtr() +{ + return new Person(); +} + +void Person::deletePtr(const Person* ptr) +{ + delete ptr; +} + std::string Person::getProfile(bool pNoAddress) { diff --git a/CxxTypeRegistration/CMakeLists.txt b/CxxTestRegistration/CMakeLists.txt similarity index 65% rename from CxxTypeRegistration/CMakeLists.txt rename to CxxTestRegistration/CMakeLists.txt index 51bfcbeb..96b0a6a3 100644 --- a/CxxTypeRegistration/CMakeLists.txt +++ b/CxxTestRegistration/CMakeLists.txt @@ -1,26 +1,26 @@ -# CMakeLists.txt for CxxTypeRegistration +# CMakeLists.txt for ReflectionTypeRegistration # Set the minimum required CMake version cmake_minimum_required(VERSION 3.20) # Set the project name -project(CxxTypeRegistration) +project(CxxTestRegistration) set(CMAKE_CXX_STANDARD 20) -SET(CXX_LIB_NAME CxxTypeRegistration) +SET(CXX_LIB_NAME CxxTestRegistration) ADD_LIBRARY(${PROJECT_NAME} STATIC "") INCLUDE_DIRECTORIES(inc) -INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/CxxTestProject/inc") +INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/CxxTestUtils/inc") +INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/CxxTestProps/inc") INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/common") INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/detail/inc") INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/access/inc") INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/builder/inc") -#TARGET_LINK_LIBRARIES(${CXX_EXE_NAME} CxxTestProject) -TARGET_LINK_LIBRARIES(${CXX_EXE_NAME} ReflectionTemplateLib) +TARGET_LINK_LIBRARIES(${CXX_LIB_NAME} ReflectionTemplateLib) # Add the source directory INCLUDE(src/CMakeLists.txt) \ No newline at end of file diff --git a/CxxTestRegistration/inc/TestMirrorProvider.h b/CxxTestRegistration/inc/TestMirrorProvider.h new file mode 100644 index 00000000..42072a54 --- /dev/null +++ b/CxxTestRegistration/inc/TestMirrorProvider.h @@ -0,0 +1,31 @@ +#pragma once + +#include "RTLibInterface.h" + +namespace test_mirror +{ + struct cxx + { + static const rtl::CxxMirror& mirror(); + }; + + + // Optional setup: do this if you prefer to access your registered types by unique 'ID', not by string. + struct reflected_id { + + static std::size_t date; + static std::size_t book; + static std::size_t event; + static std::size_t animal; + static std::size_t person; + static std::size_t library; + static std::size_t calender; + + static std::size_t char_t; + static std::size_t int_t; + static std::size_t std_string; + static std::size_t std_string_view; + + static const std::size_t getRecordIdFor(const std::string& pRecordName); + }; +} \ No newline at end of file diff --git a/CxxTestRegistration/src/CMakeLists.txt b/CxxTestRegistration/src/CMakeLists.txt new file mode 100644 index 00000000..4592f47c --- /dev/null +++ b/CxxTestRegistration/src/CMakeLists.txt @@ -0,0 +1,25 @@ +# CMakeLists.txt for ReflectionTypeRegistration +cmake_minimum_required(VERSION 3.20) + +project(CxxTestRegistration) + +# Create a variable containing the source files for your target +set(LOCAL_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/TestMirrorProvider.cpp" +) + +SET(LOCAL_HEADERS + "${PROJECT_SOURCE_DIR}/inc/TestMirrorProvider.h" + "${CMAKE_SOURCE_DIR}/CxxTestProps/inc/Book.h" + "${CMAKE_SOURCE_DIR}/CxxTestProps/inc/Complex.h" + "${CMAKE_SOURCE_DIR}/CxxTestProps/inc/Date.h" + "${CMAKE_SOURCE_DIR}/CxxTestProps/inc/Person.h" + "${CMAKE_SOURCE_DIR}/CxxTestProps/inc/Animal.h" +) + +# Add any additional source files if needed +target_sources(CxxTestRegistration + PRIVATE + "${LOCAL_SOURCES}" + "${LOCAL_HEADERS}" +) \ No newline at end of file diff --git a/CxxTestRegistration/src/TestMirrorProvider.cpp b/CxxTestRegistration/src/TestMirrorProvider.cpp new file mode 100644 index 00000000..db44c78e --- /dev/null +++ b/CxxTestRegistration/src/TestMirrorProvider.cpp @@ -0,0 +1,296 @@ + +#include +#include + +#include "TestMirrorProvider.h" +#include "CxxMirrorToJson.h" +#include "GlobalTestUtils.h" + +//User defined types to be reflected. +#include "Date.h" +#include "Book.h" +#include "Person.h" +#include "Complex.h" +#include "Animal.h" +#include "Library.h" + +/* +TestUtils, provides the interface to test/compare reflected type objects with actual objects (created via strict typing) +without exposing the actual type objects to "CxxReflectionTests" project.*/ +#include "TestUtilsBook.h" +#include "TestUtilsDate.h" +#include "TestUtilsPerson.h" +#include "TestUtilsAnimal.h" + + +using namespace std; +using namespace test_utils; + +namespace test_mirror +{ + const rtl::CxxMirror& cxx::mirror() + { + static auto cxx_mirror = rtl::CxxMirror({ + + /* --------------------------------- + Registering pod & few STL types. + --------------------------------- */ + + // Registering void, valid but not useful at all. + rtl::type().record("int").build(), + + // Registering type 'void' again, ignored & emits- + // [WARNING] Multiple registrations of the same type detected. + rtl::type().record("int").build(), + + // Registering type 'void' again, but with different name. ignored & emits- + // [WARNING] Multiple registrations of the same type detected. + rtl::type().record("ccint").build(), + + // Registering pod, reflecting- constructor, copy-constructor & destructor. + rtl::type().record("char").build(), + + rtl::type().ns("std").record("string_view").build(), + + // Registers std::string class + rtl::type().member().methodConst("empty").build(&std::string::empty), + + /* Attempting to register the same type(`std::string`) again under a different name. + * RTL will ignore this duplicate registration and retain the first one. Emits a warning on the console: + * "[WARNING] Multiple registrations of the same type with different names detected." + */ rtl::type().member().methodConst("empty").build(&std::string::empty), + + rtl::type().ns("std").record("string").build(), + + /* Attempting to register std::string_view, but the provided member function pointer belongs to std::string. + * RTL will ignore this registration. Emits a warning on the console: + * "[WARNING] Member function pointer does not belong to the class being registered!" + */ rtl::type().member().methodConst("empty").build(&std::string::empty), + + //// Finally, register std::string_view with correct member-function-pointer + // rtl::type().ns("ccstd").record().methodConst("empty").build(&std::string_view::empty), + + // Finally, register std::string_view with correct member-function-pointer + rtl::type().member().methodConst("empty").build(&std::string_view::empty), + + + /* ----------------------------------------------------------------- + Registering global, C-like functions, with & without namespaces. + ----------------------------------------------------------------- */ + + // Function taking no arguments. '' must be specified if other overload exists else not needed. compiler error otherwise. + rtl::type().function(str_reverseString).build(reverseString), + + // Overloaded function, takes 'string' arguments. '' must be specified as template parameter. + rtl::type().function(str_reverseString).build(reverseString), + + // Overloaded function, takes 'const char*' arguments. + rtl::type().function(str_reverseString).build(reverseString), + + // Unique function, no overloads, no need to specify signature as template parameters. + rtl::type().function(str_getComplexNumAsString).build(getComplexNumAsString), + + /* Grouping functions under a namespace, which is optional. they can be registered without it as well. + but if registered under namspace, then to retrieve it from CxxMirror object, namespace name must be passed, + e.g. cxx::mirror().getFunction("namespace_name", "function_name") & cxx::mirror().getRecord("namespace_name", "record_name") + */ rtl::type().ns(str_complex).function(str_setReal).build(complex::setReal), + rtl::type().ns(str_complex).function(str_setImaginary).build(complex::setImaginary), + rtl::type().ns(str_complex).function(str_getMagnitude).build(complex::getMagnitude), + + + /* ----------------------------------------------------------------------------------------------------------- + Registering user defined types. class/struct- generally termed as 'Record' as per LLVM's naming convention + ----------------------------------------------------------------------------------------------------------- */ + + // Constructors registration, class/struct name and type must be passed 'record("NAME")'. + // Registers default constructor with implicit registration of destructor & copy-constructor. + rtl::type().ns(date::ns).record(date::struct_).build(), + + // Overloaded constructor, taking 'string' as argument, signature must be specified as template parameter. + rtl::type().member().constructor().build(), + + // Again, register an overloaded constructor with diffeent signature. + rtl::type().member().constructor().build(), + + // Registring, Unique method, no overloads. Taking param 'std::string', auto deduced via function-pointer. + rtl::type().member().method(date::str_updateDate).build(&nsdate::Date::updateDate), + + // Registring const-method, 'methodConst()' function must be used. compiler error otherwise. + rtl::type().member().methodConst(date::str_getAsString).build(&nsdate::Date::getAsString), + + // Registring static-method, 'methodStatic()' function must be used. compiler error otherwise. + rtl::type().member().methodStatic(calender::str_create).build(&nsdate::Calender::create), + + // Registring unique methods of class Calender, no overloads. + rtl::type().member().method(calender::str_getTheEvent).build(&nsdate::Calender::getTheEvent), + rtl::type().member().method(calender::str_getTheDate).build(&nsdate::Calender::getTheDate), + rtl::type().member().method(calender::str_getSavedEvent).build(&nsdate::Calender::getSavedEvent), + rtl::type().member().method(calender::str_getSavedDate).build(&nsdate::Calender::getSavedDate), + + // class Calender, registering after the methods. (order doesn't matter) + rtl::type().ns(date::ns).record(calender::struct_).build(), + + // Registering 'Event' for reflection; instance creation fails since its default constructor is private or deleted. + // At least one member must be registered for RTL to recognize the type. be it property, member-function or constructor. + rtl::type().ns(event::ns).record(event::struct_).build(), + rtl::type().member().method(event::str_reset).build(&nsdate::Event::reset), + + // Registering Library's constructor. Stack allocation (rtl::alloc::Stack) will fail since its copy constructor is deleted + // and its required by 'std::any' to store its object via copy-construction. But instance on heap (rtl::alloc::HEAP) can be + // constructed since, in that case, 'std::any' stores only the poiner which does not requires copy constructor to be called. + rtl::type().record(library::class_).build(), + + // Registring static-method, 'methodStatic()' function must be used. compiler error otherwise. + rtl::type().member().methodStatic(library::str_addBook).build(&Library::addBook), + rtl::type().member().methodStatic(library::str_getBookByTitle).build(&Library::getBookByTitle), + + // class 'Book', methods & constructors. + // Registering default constructor. + rtl::type().record(book::class_).build(), + + // Registering overloaded constructor, signature must be specified as template parameter. + rtl::type().member().constructor().build(), + + // Unique methods, no overloads. + rtl::type().member().method(book::str_setAuthor).build(&Book::setAuthor), + + // Unique method, taking 'std::string' & 'const std::string&' as argument, auto deduced via function-pointer. + rtl::type().member().method(book::str_addPreface).build(&Book::addPreface), + + // Furthur registrations of unique-menthods, signature auto-deduced via function pointer. + rtl::type().member().method(book::str_setDescription).build(&Book::setDescription), + rtl::type().member().method(book::str_getPublishedOn).build(&Book::getPublishedOn), + rtl::type().member().method(book::str_addCopyrightTag).build(&Book::addCopyrightTag), + + // Registering overloaded methods, signature must be specified as template params since other overloads exists, else compiler error. + rtl::type().member().method(book::str_updateBookInfo).build(&Book::updateBookInfo), + rtl::type().member().method(book::str_updateBookInfo).build(&Book::updateBookInfo), + rtl::type().member().method(book::str_updateBookInfo).build(&Book::updateBookInfo), + + // class 'Person', methods & constructors. + rtl::type().record(person::class_).build(), + rtl::type().member().constructor().build(), + rtl::type().member().methodStatic(person::str_createPtr).build(&Person::createPtr), + rtl::type().member().method(person::str_updateAddress).build(&Person::updateAddress), + rtl::type().member().method(person::str_updateAddress).build(&Person::updateAddress), + rtl::type().member().method(person::str_getFirstName).build(&Person::getFirstName), + + // Registring const-method, 'methodConst()' function must be used. compiler error otherwise. + rtl::type().member().methodConst(person::str_updateLastName).build(&Person::updateLastName), + + // Registring const-method overload, non-const overloaded method already registered above. + rtl::type().member().methodConst(person::str_updateAddress).build(&Person::updateAddress), + rtl::type().member().methodConst(person::str_updateAddress).build(&Person::updateAddress), + rtl::type().member().methodStatic(person::str_getDefaults).build(&Person::getDefaults), + rtl::type().member().methodStatic(person::str_createConst).build(&Person::createConst), + rtl::type().member().methodStatic(person::str_getProfile).build(&Person::getProfile), + rtl::type().member().methodStatic(person::str_getProfile).build(&Person::getProfile), + rtl::type().member().methodStatic(person::str_getProfile).build(&Person::getProfile), + + // class 'Animal', methods & constructors. + rtl::type().record(animal::class_).build(), + rtl::type().member().constructor().build(), //overloaded constructor. + rtl::type().member().method(animal::str_setFamilyName).build(&Animal::setFamilyName), //unique method, no overloads. + + // Unique const-method, no overloads. + rtl::type().member().methodConst(animal::str_getFamilyName).build(&Animal::getFamilyName), + + // Overloaded method, taking const-ref as argument. + rtl::type().member().method(animal::str_setAnimalName).build(&Animal::setAnimalName), + + // Static method, taking const-ref as argument. + rtl::type().member().methodStatic(animal::str_updateZooKeeper).build(&Animal::updateZooKeeper), + + #if defined(__GNUC__) && !defined(__clang__) + /* GCC fails to automatically identify the correct overloaded functor to pick. (non-const-lvalue-ref & rvalue as argument) + we need to explicitly cast the functor like, static_cast(&Animal::setAnimalName). + */ rtl::type().member() + .method(animal::str_setAnimalName) + .build(static_cast(&Animal::setAnimalName)), //overloaded method, taking non-const lvalue reference as argument. + + rtl::type().member() + .method(animal::str_setAnimalName) + .build(static_cast(&Animal::setAnimalName)), //overloaded method, taking rvalue reference as argument. + + rtl::type().member() + .methodStatic(animal::str_updateZooKeeper) + .build(static_cast(&Animal::updateZooKeeper)), //static method, taking non-const lvalue reference as argument. + + rtl::type().member() + .methodStatic(animal::str_updateZooKeeper) + .build(static_cast(&Animal::updateZooKeeper)), //static method, taking rvalue reference as argument. + #else + rtl::type().member() + .method(animal::str_setAnimalName) + .build(&Animal::setAnimalName), //overloaded method, taking non-const lvalue reference as argument. + + rtl::type().member() + .method(animal::str_setAnimalName) + .build(&Animal::setAnimalName), //overloaded method, taking rvalue reference as argument. + + rtl::type().member() + .methodStatic(animal::str_updateZooKeeper) + .build(&Animal::updateZooKeeper), //static method, taking non-const lvalue reference as argument. + + rtl::type().member() + .methodStatic(animal::str_updateZooKeeper) + .build(&Animal::updateZooKeeper), //static method, taking rvalue reference as argument. + #endif + }); + + + static const auto _= [&]() + { + const std::string pathStr = std::filesystem::current_path().string() + "/MyReflection.json"; + std::cout << "\n[ OUTPUT] test_mirror::cxx::mirror() ==> dumping 'CxxMirror' as JSON." + << "\n file path: " << pathStr << "\n" << std::endl; + rtl::CxxMirrorToJson::dump(cxx_mirror, pathStr); + return 0; + }(); + + return cxx_mirror; + } +} + + + +namespace test_mirror +{ + //Optional setup for accessing registered types via unique-ids. + std::size_t reflected_id::book = rtl::detail::TypeId::get(); + std::size_t reflected_id::person = rtl::detail::TypeId::get(); + std::size_t reflected_id::animal = rtl::detail::TypeId::get(); + std::size_t reflected_id::library = rtl::detail::TypeId::get(); + + std::size_t reflected_id::date = rtl::detail::TypeId::get(); + std::size_t reflected_id::event = rtl::detail::TypeId::get(); + std::size_t reflected_id::calender = rtl::detail::TypeId::get(); + + std::size_t reflected_id::int_t = rtl::detail::TypeId::get(); + std::size_t reflected_id::char_t = rtl::detail::TypeId::get(); + std::size_t reflected_id::std_string = rtl::detail::TypeId::get(); + std::size_t reflected_id::std_string_view = rtl::detail::TypeId::get(); + + //Optional setup - mapping unique-ids to string type-names (for Testing-Purposes only). + const std::size_t reflected_id::getRecordIdFor(const std::string& pRecordName) + { + static std::unordered_map nameIdMap( + { + { "char", char_t }, + { "int", int_t }, + { "string", std_string }, + { "string_view", std_string_view }, + + { book::class_, book }, + { date::struct_, date }, + { event::struct_, event }, + { animal::class_, animal }, + { person::class_, person }, + { library::class_, library }, + { calender::struct_, calender } + }); + + const auto& itr = nameIdMap.find(pRecordName); + return (itr == nameIdMap.end() ? rtl::index_none:itr->second); + } +} \ No newline at end of file diff --git a/CxxTestUtils/CMakeLists.txt b/CxxTestUtils/CMakeLists.txt new file mode 100644 index 00000000..b43de122 --- /dev/null +++ b/CxxTestUtils/CMakeLists.txt @@ -0,0 +1,21 @@ +# CMakeLists.txt for CxxTestUtils + +# Set the minimum required CMake version +cmake_minimum_required(VERSION 3.20) + +# Set the project name +project(CxxTestUtils) + +set(CMAKE_CXX_STANDARD 20) + +SET(CXX_LIB_NAME CxxTestUtils) + +ADD_LIBRARY(${PROJECT_NAME} STATIC "") + +INCLUDE_DIRECTORIES(inc) +INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/CxxTestProps/inc") +INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/common") +INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/access/inc") +INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/detail/inc") +# Add the source directory +INCLUDE(src/CMakeLists.txt) \ No newline at end of file diff --git a/CxxTypeRegistration/inc/TestUtilsGlobals.h b/CxxTestUtils/inc/GlobalTestUtils.h similarity index 98% rename from CxxTypeRegistration/inc/TestUtilsGlobals.h rename to CxxTestUtils/inc/GlobalTestUtils.h index 5cf78635..c1fdda10 100644 --- a/CxxTypeRegistration/inc/TestUtilsGlobals.h +++ b/CxxTestUtils/inc/GlobalTestUtils.h @@ -1,5 +1,7 @@ #pragma once +#include + /* TestUtils provide the interface to test/compare reflected type objects with actual objects (retrived/created using strict Types) without exposing the actual type objects to "CxxReflectionTests" project. @@ -26,5 +28,4 @@ namespace test_utils { static constexpr const char* str_setReal = "setReal"; static constexpr const char* str_setImaginary = "setImaginary"; static constexpr const char* str_getMagnitude = "getMagnitude"; - } \ No newline at end of file diff --git a/CxxTestUtils/inc/Node.h b/CxxTestUtils/inc/Node.h new file mode 100644 index 00000000..2571297c --- /dev/null +++ b/CxxTestUtils/inc/Node.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +namespace test_utils +{ + struct Node + { + ~Node(); + Node(int pData); + Node(Node&& pOther) noexcept = delete; + Node(const Node& pOther) = delete; + Node& operator=(Node&&) = delete; + Node& operator=(const Node&) = delete; + + int data() const; + static bool instanceCount(); + static bool assertResourcesReleased(); + static bool getMoveOpsCountAndReset(); + + private: + int* m_data; + std::function m_deleter; + }; + + + struct Edge + { + + private: + Node* m_data; + }; +} diff --git a/CxxTestUtils/inc/TestUtilsAnimal.h b/CxxTestUtils/inc/TestUtilsAnimal.h new file mode 100644 index 00000000..9fd3a837 --- /dev/null +++ b/CxxTestUtils/inc/TestUtilsAnimal.h @@ -0,0 +1,44 @@ +#pragma once +/* +TestUtils provide the interface to test/compare reflected type objects with actual objects (retrived/created using +strict Types) without exposing the actual type objects to "CxxReflectionTests" project. + +Provides interface for Testing/Comparing the class "Animal" objects states/returns without exposing the actual type "Animal". +*/ + +#include + +namespace rtl { + class RObject; +} + + +namespace test_utils +{ + struct animal + { + static constexpr const int AGE = 0.0; + static constexpr const float WEIGHT = 0.0; + static constexpr const bool IS_MAMMAL = false; + static constexpr const char* NAME = "Orangutan"; + static constexpr const char* FAMILY_NAME = "Great Ape"; + static constexpr const char* ZOO_KEEPER = "Donald McAdams"; + + static constexpr const char* class_ = "Animal"; + static constexpr const char* str_updateZooKeeper = "updateZooKeeper"; + static constexpr const char* str_setAnimalName = "setAnimalName"; + static constexpr const char* str_setFamilyName = "setFamilyName"; + static constexpr const char* str_getFamilyName = "getFamilyName"; + + static const bool assert_zero_instance_count(); + + static const bool test_method_setAnimalName_rvalue_args(const rtl::RObject& pInstance); + + static const bool test_method_setAnimalName_const_lvalue_ref_args(const rtl::RObject& pInstance); + + static const bool test_method_setAnimalName_non_const_lvalue_ref_args(const rtl::RObject& pInstance); + + template + static const bool test_method_updateZooKeeper(const std::string& pZooKeeper); + }; +} \ No newline at end of file diff --git a/CxxTypeRegistration/inc/TestUtilsBook.h b/CxxTestUtils/inc/TestUtilsBook.h similarity index 57% rename from CxxTypeRegistration/inc/TestUtilsBook.h rename to CxxTestUtils/inc/TestUtilsBook.h index ced1359b..db7ae08c 100644 --- a/CxxTypeRegistration/inc/TestUtilsBook.h +++ b/CxxTestUtils/inc/TestUtilsBook.h @@ -1,19 +1,26 @@ #pragma once - -#include -#include /* TestUtils provide the interface to test/compare reflected type objects with actual objects (retrived/created using strict Types) without exposing the actual type objects to "CxxReflectionTests" project. Provides interface for Testing/Comparing the class "Book" objects states/returns without exposing the actual type "Book". */ + +#include + +namespace rtl { + class RObject; +} + namespace test_utils { struct library { static constexpr const char* class_ = "Library"; static constexpr const char* str_addBook = "addBook"; + static constexpr const char* str_getBookByTitle = "getBookByTitle"; + + static const bool assert_zero_instance_count(); }; struct book @@ -22,26 +29,37 @@ namespace test_utils static constexpr const char* TITLE = "Somehow, I manage."; static constexpr const char* AUTHOR = "Micheal G. Scott"; static constexpr const char* DESCRIPTION = "World's greatest boss Michael G. Scott, Regional Manager, shares his wisdom with you."; + static constexpr const char* COPYRIGHT_TAG = "Copyright (c) Micheal Scott Paper Company Pvt. Ltd."; + static constexpr const char* PREFACE = "This is a preface."; + static constexpr const char* ACKNOWLEDGEMENTS = "This is an acknowledgement."; static constexpr const char* class_ = "Book"; static constexpr const char* str_setAuthor = "setAuthor"; + static constexpr const char* str_addPreface = "addPreface"; static constexpr const char* str_setDescription = "setDescription"; static constexpr const char* str_getPublishedOn = "getPublishedOn"; static constexpr const char* str_setPublishedOn = "setPublishedOn"; static constexpr const char* str_updateBookInfo = "updateBookInfo"; + static constexpr const char* str_addCopyrightTag = "addCopyrightTag"; + + static const int get_book_instance_count(); static const bool assert_zero_instance_count(); - static const bool test_method_setAuthor(const std::any& pInstance); + static const bool test_method_setAuthor(const rtl::RObject& pInstance); + + static const bool test_method_addPreface(const rtl::RObject& pInstance); + + static const bool test_method_addCopyrightTag(const rtl::RObject& pInstance); static const bool test_method_getPublishedOn_return(const std::string& pRetStr); template - static const bool test_method_updateBookInfo(const std::any& pInstance); + static const bool test_method_updateBookInfo(const rtl::RObject& pInstance); template - static const bool test_dynamic_alloc_instance_ctor(const std::any& pInstance); + static const bool test_dynamic_alloc_instance_ctor(const rtl::RObject& pInstance); - static const bool test_unique_copy_ctor_const_ref(const std::any& pInstance); + static const bool test_copy_ctor_with_mutated_object(const rtl::RObject& pInstance); }; } \ No newline at end of file diff --git a/CxxTestUtils/inc/TestUtilsDate.h b/CxxTestUtils/inc/TestUtilsDate.h new file mode 100644 index 00000000..8281dc5b --- /dev/null +++ b/CxxTestUtils/inc/TestUtilsDate.h @@ -0,0 +1,63 @@ +#pragma once + +/* +TestUtils provide the interface to test/compare reflected type objects with actual objects (retrived/created using +strict Types) without exposing the actual type objects to "CxxReflectionTests" project. + +Provides interface for Testing/Comparing the class "Date" objects states/returns without exposing the actual type "Date". +*/ + +namespace rtl { + class RObject; +} + +namespace test_utils +{ + struct event + { + static constexpr const char* ns = "nsdate"; + static constexpr const char* struct_ = "Event"; + static constexpr const char* str_getDate = "getDate"; + static constexpr const char* str_reset = "reset"; + + static const bool assert_zero_instance_count(); + static const std::size_t get_instance_count(); + }; + + struct calender + { + static constexpr const char* ns = "nsdate"; + static constexpr const char* struct_ = "Calender"; + static constexpr const char* str_create = "create"; + static constexpr const char* str_getTheDate = "getTheDate"; + static constexpr const char* str_getSavedDate = "getSavedDate"; + static constexpr const char* str_getTheEvent = "getTheEvent"; + static constexpr const char* str_getSavedEvent = "getSavedEvent"; + + static void reset_move_ops_counter(); + static const bool assert_zero_instance_count(); + static const std::size_t get_instance_count(); + static const std::size_t get_move_ops_count(); + }; + + struct date + { + static constexpr const unsigned DAY = 1; + static constexpr const unsigned MONTH = 1; + static constexpr const unsigned YEAR = 2000; + static constexpr const char* DATE_STR0 = "23/12/2024"; + static constexpr const char* DATE_STR1 = "04/05/2025"; + + static constexpr const char* ns = "nsdate"; + static constexpr const char* struct_ = "Date"; + static constexpr const char* str_updateDate = "updateDate"; + static constexpr const char* str_getAsString = "getAsString"; + + static const std::size_t get_instance_count(); + + static const bool test_if_obejcts_are_equal(const rtl::RObject& pInstance0, const rtl::RObject& pInstance1); + + template + static const bool test_dynamic_alloc_instance_ctor(const rtl::RObject& pInstance); + }; +} \ No newline at end of file diff --git a/CxxTypeRegistration/inc/TestUtilsPerson.h b/CxxTestUtils/inc/TestUtilsPerson.h similarity index 72% rename from CxxTypeRegistration/inc/TestUtilsPerson.h rename to CxxTestUtils/inc/TestUtilsPerson.h index 3098b716..1f50f20d 100644 --- a/CxxTypeRegistration/inc/TestUtilsPerson.h +++ b/CxxTestUtils/inc/TestUtilsPerson.h @@ -1,13 +1,17 @@ #pragma once - -#include -#include /* TestUtils provide the interface to test/compare reflected type objects with actual objects (retrived/created using strict Types) without exposing the actual type objects to "CxxReflectionTests" project. Provides interface for Testing/Comparing the class "Person" objects states/returns without exposing the actual type "Person". */ + +#include + +namespace rtl { + class RObject; +} + namespace test_utils { struct person @@ -19,7 +23,9 @@ namespace test_utils static constexpr const char* OCCUPATION = "Private Detective."; static constexpr const char* class_ = "Person"; + static constexpr const char* str_createPtr = "createPtr"; static constexpr const char* str_getProfile = "getProfile"; + static constexpr const char* str_createConst = "createConst"; static constexpr const char* str_getDefaults = "getDefaults"; static constexpr const char* str_getFirstName = "getFirstName"; static constexpr const char* str_updateAddress = "updateAddress"; @@ -29,21 +35,21 @@ namespace test_utils static const std::string get_str_returned_on_call_getDefaults(); + static const bool delete_unmanaged_person_instance_created_via_createPtr(const rtl::RObject& pInstance); + template static const std::string get_str_returned_on_call_getProfile(const bool pNoAddress = false); - static const bool test_method_updateLastName(const std::any& pInstance); - - static const bool test_method_updateLastName_const(const std::any& pInstance); + static const bool test_method_updateLastName_const(const rtl::RObject& pInstance); template - static const bool test_method_updateAddress(const std::any& pInstance); + static const bool test_method_updateAddress(const rtl::RObject& pInstance); template - static const bool test_method_updateAddress_const(const std::any& pInstance); + static const bool test_method_updateAddress_const(const rtl::RObject& pInstance); - static const bool test_copy_constructor_overload_src_const_obj(const std::any& pInstance); + static const bool test_copy_constructor_overload_src_const_obj(const rtl::RObject& pInstance); - static const bool test_copy_constructor_overload_src_non_const_obj(const std::any& pInstance); + static const bool test_copy_constructor_overload_src_non_const_obj(const rtl::RObject& pInstance); }; } \ No newline at end of file diff --git a/CxxTestUtils/src/CMakeLists.txt b/CxxTestUtils/src/CMakeLists.txt new file mode 100644 index 00000000..f24a709c --- /dev/null +++ b/CxxTestUtils/src/CMakeLists.txt @@ -0,0 +1,41 @@ +# CMakeLists.txt for CxxTestUtils +cmake_minimum_required(VERSION 3.20) + +project(CxxTestUtils) + +# Create a variable containing the source files for your target +set(LOCAL_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/Node.cpp" + "${CMAKE_CURRENT_LIST_DIR}/TestUtilsBook.cpp" + "${CMAKE_CURRENT_LIST_DIR}/TestUtilsDate.cpp" + "${CMAKE_CURRENT_LIST_DIR}/TestUtilsPerson.cpp" + "${CMAKE_CURRENT_LIST_DIR}/TestUtilsAnimal.cpp" + "${CMAKE_SOURCE_DIR}/CxxTestProps/src/Book.cpp" + "${CMAKE_SOURCE_DIR}/CxxTestProps/src/Complex.cpp" + "${CMAKE_SOURCE_DIR}/CxxTestProps/src/Date.cpp" + "${CMAKE_SOURCE_DIR}/CxxTestProps/src/Person.cpp" + "${CMAKE_SOURCE_DIR}/CxxTestProps/src/Animal.cpp" + "${CMAKE_SOURCE_DIR}/CxxTestProps/src/Library.cpp" +) + +SET(LOCAL_HEADERS + "${PROJECT_SOURCE_DIR}/inc/Node.h" + "${PROJECT_SOURCE_DIR}/inc/TestUtilsBook.h" + "${PROJECT_SOURCE_DIR}/inc/TestUtilsDate.h" + "${PROJECT_SOURCE_DIR}/inc/GlobalTestUtils.h" + "${PROJECT_SOURCE_DIR}/inc/TestUtilsPerson.h" + "${PROJECT_SOURCE_DIR}/inc/TestUtilsAnimal.h" + "${CMAKE_SOURCE_DIR}/CxxTestProps/inc/Book.h" + "${CMAKE_SOURCE_DIR}/CxxTestProps/inc/Complex.h" + "${CMAKE_SOURCE_DIR}/CxxTestProps/inc/Date.h" + "${CMAKE_SOURCE_DIR}/CxxTestProps/inc/Person.h" + "${CMAKE_SOURCE_DIR}/CxxTestProps/inc/Animal.h" + "${CMAKE_SOURCE_DIR}/CxxTestProps/inc/Library.h" +) + +# Add any additional source files if needed +target_sources(CxxTestUtils + PRIVATE + "${LOCAL_SOURCES}" + "${LOCAL_HEADERS}" +) \ No newline at end of file diff --git a/CxxTestUtils/src/Node.cpp b/CxxTestUtils/src/Node.cpp new file mode 100644 index 00000000..ef837b2e --- /dev/null +++ b/CxxTestUtils/src/Node.cpp @@ -0,0 +1,59 @@ + +#include "Node.h" + +namespace test_utils +{ + std::size_t _moveOpsCount = 0; + std::size_t _liveResourceCount = 0; + std::size_t _liveNodeCount = 0; + + Node::~Node() + { + _liveNodeCount--; + if (m_deleter && m_data) { + m_deleter(m_data); + m_data = nullptr; + m_deleter = nullptr; + } + } + + Node::Node(int pData) + : m_data([=]() { + _liveResourceCount++; + return new int(pData); + }()) + , m_deleter([](int* ptr) { + _liveResourceCount--; + delete ptr; + }) { + _liveNodeCount++; + } + + //Node::Node(Node&& pOther) noexcept + // : m_data(pOther.m_data) + // , m_deleter(std::move(pOther.m_deleter)) { + // pOther.m_data = nullptr; + // pOther.m_deleter = nullptr; + // _liveNodeCount++; + // _moveOpsCount++; + //} + + int Node::data() const { + return *m_data; + } + + bool Node::instanceCount() { + return _liveNodeCount; + } + + bool Node::assertResourcesReleased() { + return (_liveResourceCount == 0); + } + + bool Node::getMoveOpsCountAndReset() { + std::size_t count = _moveOpsCount; + _moveOpsCount = 0; + return count; + } +} + diff --git a/CxxTestUtils/src/TestUtilsAnimal.cpp b/CxxTestUtils/src/TestUtilsAnimal.cpp new file mode 100644 index 00000000..f423fa2b --- /dev/null +++ b/CxxTestUtils/src/TestUtilsAnimal.cpp @@ -0,0 +1,80 @@ + +#include "RObject.hpp" + +#include "TestUtilsAnimal.h" +#include "Animal.h" +#include "Library.h" + +static auto _= Library::getBooksCount(); + +const bool test_utils::animal::assert_zero_instance_count() +{ + return (Animal::getInstanceCount() == 0); +} + + +template<> +const bool test_utils::animal::test_method_updateZooKeeper(const std::string& pZooKeeper) +{ + std::string zooKeeper = ZOO_KEEPER; + return (pZooKeeper == Animal::updateZooKeeper(zooKeeper)); +} + + +template<> +const bool test_utils::animal::test_method_updateZooKeeper(const std::string& pZooKeeper) +{ + return (pZooKeeper == Animal::updateZooKeeper(ZOO_KEEPER)); +} + + +template<> +const bool test_utils::animal::test_method_updateZooKeeper(const std::string& pZooKeeper) +{ + const std::string zooKeeper = ZOO_KEEPER; + return (pZooKeeper == Animal::updateZooKeeper(zooKeeper)); +} + + +const bool test_utils::animal::test_method_setAnimalName_rvalue_args(const rtl::RObject& pInstance) +{ + if (pInstance.canViewAs()) + { + Animal animal; + animal.setAnimalName(std::string(NAME)); + + const Animal& rAnimal = pInstance.view()->get(); + return (animal == rAnimal); + } + return false; +} + + +const bool test_utils::animal::test_method_setAnimalName_const_lvalue_ref_args(const rtl::RObject& pInstance) +{ + if (pInstance.canViewAs()) + { + Animal animal; + const auto& nameStr = std::string(NAME); + animal.setAnimalName(nameStr); + + const Animal& rAnimal = pInstance.view()->get(); + return (animal == rAnimal); + } + return false; +} + + +const bool test_utils::animal::test_method_setAnimalName_non_const_lvalue_ref_args(const rtl::RObject& pInstance) +{ + if (pInstance.canViewAs()) + { + Animal animal; + auto nameStr = std::string(NAME); + animal.setAnimalName(nameStr); + + const Animal& rAnimal = pInstance.view()->get(); + return (animal == rAnimal); + } + return false; +} diff --git a/CxxTestUtils/src/TestUtilsBook.cpp b/CxxTestUtils/src/TestUtilsBook.cpp new file mode 100644 index 00000000..268f04d1 --- /dev/null +++ b/CxxTestUtils/src/TestUtilsBook.cpp @@ -0,0 +1,155 @@ + +#include "TestUtilsBook.h" + +#include "RObject.hpp" + +//User defined types. +#include "Book.h" +#include "Library.h" + +using namespace std; +using namespace nsdate; + +namespace test_utils +{ + const bool library::assert_zero_instance_count() + { + return (Library::getInstanceCount() == 0); + } + + const int book::get_book_instance_count() + { + return Book::getInstanceCount(); + } + + const bool book::assert_zero_instance_count() + { + return (Book::getInstanceCount() == 0); + } + + + const bool book::test_method_getPublishedOn_return(const std::string& pRetStr) + { + Book bookObj; + return (bookObj.getPublishedOn() == pRetStr); + } + + + template<> + const bool book::test_dynamic_alloc_instance_ctor<>(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + const auto& rbook = pInstance.view()->get(); + return (Book() == rbook); + } + return false; + } + + + template<> + const bool book::test_dynamic_alloc_instance_ctor(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + const auto& rbook = pInstance.view()->get(); + return (Book(PRICE, TITLE) == rbook); + } + return false; + } + + + const bool book::test_method_setAuthor(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + Book book; + book.setAuthor(AUTHOR); + const auto& rbook = pInstance.view()->get(); + return (book == rbook); + } + return false; + } + + const bool book::test_method_addCopyrightTag(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + Book book; + book.addCopyrightTag(COPYRIGHT_TAG); + const auto& rbook = pInstance.view()->get(); + return (book == rbook); + } + return false; + } + + + const bool book::test_method_addPreface(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + Book book; + book.addPreface(ACKNOWLEDGEMENTS, PREFACE); + const auto& rbook = pInstance.view()->get(); + return (book == rbook); + } + return false; + } + + + template<> + const bool book::test_method_updateBookInfo<>(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + Book book; + book.updateBookInfo(); + const auto& rbook = pInstance.view()->get(); + return (book == rbook); + } + return false; + } + + + template<> + const bool book::test_method_updateBookInfo(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + Book book; + book.updateBookInfo(TITLE, PRICE, string(AUTHOR)); + const auto& rbook = pInstance.view()->get(); + return (book == rbook); + } + return false; + } + + + template<> + const bool book::test_method_updateBookInfo(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + Book book; + book.updateBookInfo(string(AUTHOR), PRICE, TITLE); + const auto& rbook = pInstance.view()->get(); + return (book == rbook); + } + return false; + } + + + const bool test_utils::book::test_copy_ctor_with_mutated_object(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + Book obj(PRICE, TITLE); + obj.setAuthor(AUTHOR); + obj.setDescription(DESCRIPTION); + Book copyObj(obj); + const auto& rbook = pInstance.view()->get(); + return (copyObj == rbook); + } + return false; + } +} \ No newline at end of file diff --git a/CxxTestUtils/src/TestUtilsDate.cpp b/CxxTestUtils/src/TestUtilsDate.cpp new file mode 100644 index 00000000..4787615b --- /dev/null +++ b/CxxTestUtils/src/TestUtilsDate.cpp @@ -0,0 +1,89 @@ + +#include "RObject.hpp" + +//User defined types. +#include "Date.h" +#include "TestUtilsDate.h" + +using namespace std; +using namespace nsdate; + +namespace test_utils +{ + const bool calender::assert_zero_instance_count() + { + return (Calender::instanceCount() == 0); + } + + const std::size_t calender::get_instance_count() + { + return Calender::instanceCount(); + } + + void calender::reset_move_ops_counter() + { + Calender::resetMoveOpsCounter(); + } + + const std::size_t calender::get_move_ops_count() + { + return Calender::getMoveOpsCount(); + } + + const bool event::assert_zero_instance_count() + { + return (Event::instanceCount() == 0); + } + + const std::size_t event::get_instance_count() + { + return Event::instanceCount(); + } + + const std::size_t date::get_instance_count() + { + return Date::instanceCount(); + } + + const bool date::test_if_obejcts_are_equal(const rtl::RObject& pInstance0, const rtl::RObject& pInstance1) + { + if (pInstance0.canViewAs() && pInstance1.canViewAs()) + { + const Date& rdate0 = pInstance0.view()->get(); + const Date& rdate1 = pInstance1.view()->get(); + return (rdate0 == rdate1); + } + return false; + } + + template<> + const bool date::test_dynamic_alloc_instance_ctor<>(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) { + const Date& rdate = pInstance.view()->get(); + return (Date() == rdate); + } + return false; + } + + template<> + const bool date::test_dynamic_alloc_instance_ctor(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) { + const Date& rdate = pInstance.view()->get(); + return (Date(DATE_STR0) == rdate); + } + return false; + } + + + template<> + const bool date::test_dynamic_alloc_instance_ctor(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) { + const Date& rdate = pInstance.view()->get(); + return (Date(DAY, MONTH, YEAR) == rdate); + } + return false; + } +} \ No newline at end of file diff --git a/CxxTestUtils/src/TestUtilsPerson.cpp b/CxxTestUtils/src/TestUtilsPerson.cpp new file mode 100644 index 00000000..bf069a90 --- /dev/null +++ b/CxxTestUtils/src/TestUtilsPerson.cpp @@ -0,0 +1,151 @@ + +#include "TestUtilsPerson.h" + +#include "RObject.hpp" + +//User defined types. +#include "Person.h" + +using namespace std; +namespace test_utils +{ + const bool person::assert_zero_instance_count() + { + return (Person::getInstanceCount() == 0); + } + + + const string person::get_str_returned_on_call_getDefaults() + { + return Person::getDefaults(); + } + + + template<> + const std::string person::get_str_returned_on_call_getProfile(const bool pNoAddress) + { + return Person::getProfile(); + } + + + template<> + const std::string person::get_str_returned_on_call_getProfile(const bool pNoAddress) + { + return Person::getProfile(pNoAddress); + } + + + template<> + const std::string person::get_str_returned_on_call_getProfile(const bool pNoAddress) + { + return Person::getProfile(OCCUPATION, AGE); + } + + + const bool person::test_method_updateLastName_const(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + const Person person(FIRST_NAME); + person.updateLastName(LAST_NAME); + const Person& rPerson = pInstance.view()->get(); + return (person == rPerson); + } + return false; + + } + + + const bool person::test_copy_constructor_overload_src_const_obj(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + const Person personSrc; + Person person(personSrc); + const Person& rPerson = pInstance.view()->get(); + return (person == rPerson); + } + return false; + } + + + const bool person::test_copy_constructor_overload_src_non_const_obj(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + Person personSrc; + Person person(personSrc); + const Person& rPerson = pInstance.view()->get(); + return (person == rPerson); + } + return false; + } + + + template<> + const bool person::test_method_updateAddress(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + Person person(FIRST_NAME); + person.updateAddress(ADDRESS); + const Person& rPerson = pInstance.view()->get(); + return (person == rPerson); + } + return false; + } + + + const bool person::delete_unmanaged_person_instance_created_via_createPtr(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + const Person& rPerson = pInstance.view()->get(); + Person::deletePtr(&rPerson); + return true; + } + return false; + } + + + template<> + const bool person::test_method_updateAddress_const(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + const Person person(FIRST_NAME); + person.updateAddress(ADDRESS); + const Person& rPerson = pInstance.view()->get(); + return (person == rPerson); + } + return false; + } + + + template<> + const bool person::test_method_updateAddress<>(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + Person person(FIRST_NAME); + person.updateAddress(); + const Person& rPerson = pInstance.view()->get(); + return (person == rPerson); + } + return false; + } + + + template<> + const bool person::test_method_updateAddress_const<>(const rtl::RObject& pInstance) + { + if (pInstance.canViewAs()) + { + const Person person(FIRST_NAME); + person.updateAddress(); + const Person& rPerson = pInstance.view()->get(); + return (person == rPerson); + } + return false; + } +} \ No newline at end of file diff --git a/CxxTypeRegistration/inc/MyReflection.h b/CxxTypeRegistration/inc/MyReflection.h deleted file mode 100644 index 9e594119..00000000 --- a/CxxTypeRegistration/inc/MyReflection.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include "RTLibInterface.h" - -struct MyReflection -{ - static rtl::access::CxxMirror& instance(); -}; diff --git a/CxxTypeRegistration/inc/TestUtilsDate.h b/CxxTypeRegistration/inc/TestUtilsDate.h deleted file mode 100644 index 48e4dfb6..00000000 --- a/CxxTypeRegistration/inc/TestUtilsDate.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include - -/* -TestUtils provide the interface to test/compare reflected type objects with actual objects (retrived/created using -strict Types) without exposing the actual type objects to "CxxReflectionTests" project. - -Provides interface for Testing/Comparing the class "Date" objects states/returns without exposing the actual type "Date". -*/ -namespace test_utils -{ - struct calender - { - static constexpr const char* ns = "nsdate"; - static constexpr const char* struct_ = "Calender"; - static const bool assert_zero_instance_count(); - }; - - struct date - { - static constexpr const unsigned DAY = 1; - static constexpr const unsigned MONTH = 1; - static constexpr const unsigned YEAR = 2000; - static constexpr const char* DATE_STR = "23/12/2024"; - - static constexpr const char* ns = "nsdate"; - static constexpr const char* struct_ = "Date"; - - static const bool assert_zero_instance_count(); - - template - static const bool test_dynamic_alloc_instance_ctor(const std::any& pInstance); - }; -} \ No newline at end of file diff --git a/CxxTypeRegistration/src/CMakeLists.txt b/CxxTypeRegistration/src/CMakeLists.txt deleted file mode 100644 index b1803ca6..00000000 --- a/CxxTypeRegistration/src/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -# CMakeLists.txt for CxxTypeRegistration -cmake_minimum_required(VERSION 3.20) - -project(CxxTypeRegistration) - -# Create a variable containing the source files for your target -set(LOCAL_SOURCES - "${CMAKE_CURRENT_LIST_DIR}/MyReflection.cpp" - "${CMAKE_CURRENT_LIST_DIR}/TestUtilsBook.cpp" - "${CMAKE_CURRENT_LIST_DIR}/TestUtilsDate.cpp" - "${CMAKE_CURRENT_LIST_DIR}/TestUtilsPerson.cpp" - "${CMAKE_SOURCE_DIR}/CxxTestProject/src/Book.cpp" - "${CMAKE_SOURCE_DIR}/CxxTestProject/src/Complex.cpp" - "${CMAKE_SOURCE_DIR}/CxxTestProject/src/Date.cpp" - "${CMAKE_SOURCE_DIR}/CxxTestProject/src/Person.cpp" -) - -SET(LOCAL_HEADERS - "${PROJECT_SOURCE_DIR}/inc/MyReflection.h" - "${PROJECT_SOURCE_DIR}/inc/TestUtilsBook.h" - "${PROJECT_SOURCE_DIR}/inc/TestUtilsDate.h" - "${PROJECT_SOURCE_DIR}/inc/TestUtilsGlobals.h" - "${PROJECT_SOURCE_DIR}/inc/TestUtilsPerson.h" - "${CMAKE_SOURCE_DIR}/CxxTestProject/inc/Book.h" - "${CMAKE_SOURCE_DIR}/CxxTestProject/inc/Complex.h" - "${CMAKE_SOURCE_DIR}/CxxTestProject/inc/Date.h" - "${CMAKE_SOURCE_DIR}/CxxTestProject/inc/Person.h" -) - -# Add any additional source files if needed -target_sources(CxxTypeRegistration - PRIVATE - "${LOCAL_SOURCES}" - "${LOCAL_HEADERS}" -) \ No newline at end of file diff --git a/CxxTypeRegistration/src/MyReflection.cpp b/CxxTypeRegistration/src/MyReflection.cpp deleted file mode 100644 index 99d015d0..00000000 --- a/CxxTypeRegistration/src/MyReflection.cpp +++ /dev/null @@ -1,91 +0,0 @@ - -#include - -#include "MyReflection.h" -#include "CxxMirrorToJson.h" - -//User defined types to be reflected. -#include "Date.h" -#include "Book.h" -#include "Person.h" -#include "Complex.h" - -/* -TestUtils, provides the interface to test/compare reflected type objects with actual objects (created via strict typing) -without exposing the actual type objects to "CxxReflectionTests" project.*/ -#include "TestUtilsBook.h" -#include "TestUtilsDate.h" -#include "TestUtilsPerson.h" -#include "TestUtilsGlobals.h" - - -using namespace std; -using namespace test_utils; -using namespace rtl::access; -using namespace rtl::builder; - -CxxMirror& MyReflection::instance() -{ - static CxxMirror cxxMirror = CxxMirror({ - - //global functions, not contained in any namespace. - Reflect().function(str_reverseString).build(reverseString), //function taking no arguments. '' must be specified if other overload exists else not needed. compiler error otherwise. - Reflect().function(str_reverseString).build(reverseString), //overloaded function, takes 'string' arguments. '' must be specified as template parameter. - Reflect().function(str_reverseString).build(reverseString), //overloaded function, takes 'const char*' arguments. - Reflect().function(str_getComplexNumAsString).build(getComplexNumAsString), //unique function, no overloads, no need to specify signature as template parameters. - - /* Grouping functions under a namespace, which is optional. they can be registered without it as well. - but if registered under namspace, then to retrieve it from CxxMirror object, namespace name must be passed, - ex - cxxMirror.getFunction("namespace_name", "function_name") & cxxMirror.getRecord("namespace_name", "record_name") - */ Reflect().nameSpace(str_complex).function(str_setReal).build(complex::setReal), - Reflect().nameSpace(str_complex).function(str_setImaginary).build(complex::setImaginary), - Reflect().nameSpace(str_complex).function(str_getMagnitude).build(complex::getMagnitude), - - //Constructors registration, class/struct name and type must be passed 'record("NAME")'. - Reflect().nameSpace(date::ns).record(date::struct_).constructor().build(), //default constructor. Destructor gets registered automatically if any constructor is registered. - Reflect().nameSpace(date::ns).record(date::struct_).constructor().build(), //overloaded constructor, taking 'string' as argument, must be specified as template param. - Reflect().nameSpace(date::ns).record(date::struct_).constructor().build(), //again, the overloaded constructor. - Reflect().nameSpace(date::ns).record(date::struct_).constructor().build(), //Copy constructor, taking non-const ref as argument. - - //class Calender, default constructor. Instances will always be created on heap and managed using shared_ptr. - Reflect().nameSpace(calender::ns).record(calender::struct_).constructor().build(), - Reflect().record(library::class_).methodStatic(library::str_addBook).build(&Library::addBook), //Static method registration, 'methodStatic()' function must be used. compiler error otherwise. - - //class 'Book', methods & constructors. - Reflect().record(book::class_).constructor().build(), - Reflect().record(book::class_).constructor().build(), //copy constructor, taking const-ref. - Reflect().record(book::class_).constructor().build(), - Reflect().record(book::class_).method(book::str_setAuthor).build(&Book::setAuthor), //unique methods, no overloads. - Reflect().record(book::class_).method(book::str_setDescription).build(&Book::setDescription), - Reflect().record(book::class_).method(book::str_getPublishedOn).build(&Book::getPublishedOn), - Reflect().record(book::class_).method(book::str_updateBookInfo).build(&Book::updateBookInfo), //method overloading, '' must be specified since other overloads exists. - Reflect().record(book::class_).method(book::str_updateBookInfo).build(&Book::updateBookInfo), - Reflect().record(book::class_).method(book::str_updateBookInfo).build(&Book::updateBookInfo), - - //class 'Person', methods & constructors. - Reflect().record(person::class_).constructor().build(), - Reflect().record(person::class_).constructor().build(), - Reflect().record(person::class_).constructor().build(), //copy constructor taking non-const ref argument. - Reflect().record(person::class_).constructor().build(), //copy constructor taking const ref argument. - Reflect().record(person::class_).method(person::str_updateAddress).build(&Person::updateAddress), - Reflect().record(person::class_).method(person::str_updateAddress).build(&Person::updateAddress), - Reflect().record(person::class_).methodConst(person::str_getFirstName).build(&Person::getFirstName), - Reflect().record(person::class_).methodConst(person::str_updateLastName).build(&Person::updateLastName), //const method registration, 'methodConst()' function must be used. compiler error otherwise. - Reflect().record(person::class_).methodConst(person::str_updateAddress).build(&Person::updateAddress), - Reflect().record(person::class_).methodConst(person::str_updateAddress).build(&Person::updateAddress), //overloaded method based on 'const'. - Reflect().record(person::class_).methodStatic(person::str_getDefaults).build(&Person::getDefaults), - Reflect().record(person::class_).methodStatic(person::str_getProfile).build(&Person::getProfile), - Reflect().record(person::class_).methodStatic(person::str_getProfile).build(&Person::getProfile), - Reflect().record(person::class_).methodStatic(person::str_getProfile).build(&Person::getProfile) - }); - - - static bool dumped = false; - if (!dumped) { - const std::string pathStr = std::filesystem::current_path().string() + "/MyReflection.json"; - rtl::CxxMirrorToJson::dump(cxxMirror, pathStr); - dumped = true; - } - - return cxxMirror; -} \ No newline at end of file diff --git a/CxxTypeRegistration/src/TestUtils.cpp b/CxxTypeRegistration/src/TestUtils.cpp deleted file mode 100644 index 9c63bf44..00000000 --- a/CxxTypeRegistration/src/TestUtils.cpp +++ /dev/null @@ -1,99 +0,0 @@ - -#include - -#include "TestUtils.h" -#include "RObject.hpp" - -//User defined types. -#include "Date.h" -#include "Book.h" - -using namespace std; -using namespace nsdate; -using namespace rtl::access; - -namespace test_utils -{ - const bool date::assert_zero_instance_count() - { - return (Date::instanceCount() == 0); - } - - template<> - const bool date::test_new_instance_ctor(const unique_ptr& pInstance) - { - optional robj = pInstance->get(); - if (!robj.has_value()) { - return false; - } - - Date dateObj(DATE_STR); - Date& dateRObj = *(robj.value()); - - return (dateObj == dateRObj); - } - - template<> - const bool date::test_new_instance_ctor<>(const unique_ptr& pInstance) - { - optional robj = pInstance->get(); - if (!robj.has_value()) { - return false; - } - - Date dateObj; - Date& dateRObj = *(robj.value()); - - return (dateObj == dateRObj); - } - - template<> - const bool date::test_new_instance_ctor(const unique_ptr& pInstance) - { - optional robj = pInstance->get(); - if (!robj.has_value()) { - return false; - } - - Date dateObj(day, month, year); - Date& dateRObj = *(robj.value()); - - return (dateObj == dateRObj); - } - - - const bool book::assert_zero_instance_count() - { - return (Book::getInstanceCount() == 0); - } - - - template<> - const bool book::test_new_instance_ctor<>(const std::unique_ptr& pInstance) - { - optional robj = pInstance->get(); - if (!robj.has_value()) { - return false; - } - - Book bookObj; - Book& bookRObj = *(robj.value()); - - return (bookObj == bookRObj); - } - - - template<> - const bool book::test_new_instance_ctor(const std::unique_ptr& pInstance) - { - optional robj = pInstance->get(); - if (!robj.has_value()) { - return false; - } - - Book bookObj(PRICE, TITLE); - Book& bookRObj = *(robj.value()); - - return (bookObj == bookRObj); - } -} \ No newline at end of file diff --git a/CxxTypeRegistration/src/TestUtilsBook.cpp b/CxxTypeRegistration/src/TestUtilsBook.cpp deleted file mode 100644 index 329c4821..00000000 --- a/CxxTypeRegistration/src/TestUtilsBook.cpp +++ /dev/null @@ -1,116 +0,0 @@ - -#include "TestUtilsBook.h" - -//User defined types. -#include "Book.h" - -using namespace std; -using namespace nsdate; - -namespace test_utils -{ - const bool book::assert_zero_instance_count() - { - return (Book::getInstanceCount() == 0); - } - - - const bool book::test_method_getPublishedOn_return(const std::string& pRetStr) - { - Book bookObj; - return (bookObj.getPublishedOn() == pRetStr); - } - - - template<> - const bool book::test_dynamic_alloc_instance_ctor<>(const any& pInstance) - { - Book* rbook = any_cast(pInstance); - if (rbook == nullptr) { - return false; - } - return (Book() == *rbook); - } - - - template<> - const bool book::test_dynamic_alloc_instance_ctor(const any& pInstance) - { - Book* rbook = any_cast(pInstance); - if (rbook == nullptr) { - return false; - } - return (Book(PRICE, TITLE) == *rbook); - } - - - const bool book::test_method_setAuthor(const any& pInstance) - { - Book* rbook = any_cast(pInstance); - if (rbook == nullptr) { - return false; - } - - Book book; - book.setAuthor(AUTHOR); - return (book == *rbook); - } - - - template<> - const bool book::test_method_updateBookInfo<>(const any& pInstance) - { - Book* rbook = any_cast(pInstance); - if (rbook == nullptr) { - return false; - } - - Book book; - book.updateBookInfo(); - return (book == *rbook); - } - - - template<> - const bool book::test_method_updateBookInfo(const any& pInstance) - { - Book* rbook = any_cast(pInstance); - if (rbook == nullptr) { - return false; - } - - Book book; - book.updateBookInfo(TITLE, PRICE, string(AUTHOR)); - return (book == *rbook); - } - - - template<> - const bool book::test_method_updateBookInfo(const any& pInstance) - { - Book* rbook = any_cast(pInstance); - if (rbook == nullptr) { - return false; - } - - Book book; - book.updateBookInfo(string(AUTHOR), PRICE, TITLE); - return (book == *rbook); - } - - - const bool book::test_unique_copy_ctor_const_ref(const std::any& pInstance) - { - Book* rbook = any_cast(pInstance); - if (rbook == nullptr) { - return false; - } - - Book obj(PRICE, TITLE); - obj.setAuthor(AUTHOR); - obj.setDescription(DESCRIPTION); - - Book copyObj(obj); - return (copyObj == *rbook); - } -} \ No newline at end of file diff --git a/CxxTypeRegistration/src/TestUtilsDate.cpp b/CxxTypeRegistration/src/TestUtilsDate.cpp deleted file mode 100644 index bda51667..00000000 --- a/CxxTypeRegistration/src/TestUtilsDate.cpp +++ /dev/null @@ -1,53 +0,0 @@ - -#include "TestUtilsDate.h" - -//User defined types. -#include "Date.h" - -using namespace std; -using namespace nsdate; - -namespace test_utils -{ - const bool calender::assert_zero_instance_count() - { - return (Calender::instanceCount() == 0); - } - - const bool date::assert_zero_instance_count() - { - return (Date::instanceCount() == 0); - } - - template<> - const bool date::test_dynamic_alloc_instance_ctor<>(const any& pInstance) - { - Date* rdate = any_cast(pInstance); - if (rdate == nullptr) { - return false; - } - return (Date() == *rdate); - } - - - template<> - const bool date::test_dynamic_alloc_instance_ctor(const any& pInstance) - { - Date* rdate = any_cast(pInstance); - if (rdate == nullptr) { - return false; - } - return (Date(DATE_STR) == *rdate); - } - - - template<> - const bool date::test_dynamic_alloc_instance_ctor(const any& pInstance) - { - Date* rdate = any_cast(pInstance); - if (rdate == nullptr) { - return false; - } - return (Date(DAY, MONTH, YEAR) == *rdate); - } -} \ No newline at end of file diff --git a/CxxTypeRegistration/src/TestUtilsPerson.cpp b/CxxTypeRegistration/src/TestUtilsPerson.cpp deleted file mode 100644 index 002439df..00000000 --- a/CxxTypeRegistration/src/TestUtilsPerson.cpp +++ /dev/null @@ -1,154 +0,0 @@ - -#include "TestUtilsPerson.h" - -//User defined types. -#include "Person.h" - -using namespace std; -namespace test_utils -{ - const bool person::assert_zero_instance_count() - { - return (Person::getInstanceCount() == 0); - } - - - const string person::get_str_returned_on_call_getDefaults() - { - return Person::getDefaults(); - } - - - template<> - const std::string person::get_str_returned_on_call_getProfile(const bool pNoAddress) - { - return Person::getProfile(); - } - - - template<> - const std::string person::get_str_returned_on_call_getProfile(const bool pNoAddress) - { - return Person::getProfile(pNoAddress); - } - - - template<> - const std::string person::get_str_returned_on_call_getProfile(const bool pNoAddress) - { - return Person::getProfile(OCCUPATION, AGE); - } - - - const bool person::test_method_updateLastName(const std::any& pInstance) - { - Person* rPerson = any_cast(pInstance); - if (rPerson == nullptr) { - return false; - } - - Person person(FIRST_NAME); - person.updateLastName(LAST_NAME); - return (person == *rPerson); - } - - - const bool person::test_method_updateLastName_const(const std::any& pInstance) - { - //instance created via reflection will always hold non-const pointer only. const(or not) is maintained internally to call appropriate method. - Person* rPerson = any_cast(pInstance); - if (rPerson == nullptr) { - return false; - } - - const Person person(FIRST_NAME); - person.updateLastName(LAST_NAME); - return (person == *rPerson); - } - - const bool person::test_copy_constructor_overload_src_const_obj(const std::any& pInstance) - { - //instance created via reflection will always hold non-const pointer only. const(or not) is maintained internally to call appropriate method. - Person* rPerson = any_cast(pInstance); - if (rPerson == nullptr) { - return false; - } - - const Person personSrc; - Person person(personSrc); - return (person == *rPerson); - } - - const bool person::test_copy_constructor_overload_src_non_const_obj(const std::any& pInstance) - { - //instance created via reflection will always hold non-const pointer only. const(or not) is maintained internally to call appropriate method. - Person* rPerson = any_cast(pInstance); - if (rPerson == nullptr) { - return false; - } - - Person personSrc; - Person person(personSrc); - return (person == *rPerson); - } - - - template<> - const bool person::test_method_updateAddress(const std::any& pInstance) - { - //instance created via reflection will always hold non-const pointer only. const(or not) is maintained internally to call appropriate method. - Person* rPerson = any_cast(pInstance); - if (rPerson == nullptr) { - return false; - } - - Person person(FIRST_NAME); - person.updateAddress(ADDRESS); - return (person == *rPerson); - } - - - template<> - const bool person::test_method_updateAddress_const(const std::any& pInstance) - { - //instance created via reflection will always hold non-const pointer only. const(or not) is maintained internally to call appropriate method. - Person* rPerson = any_cast(pInstance); - if (rPerson == nullptr) { - return false; - } - - const Person person(FIRST_NAME); - person.updateAddress(ADDRESS); - return (person == *rPerson); - } - - - template<> - const bool person::test_method_updateAddress<>(const std::any& pInstance) - { - //instance created via reflection will always hold non-const pointer only. const(or not) is maintained internally to call appropriate method. - Person* rPerson = any_cast(pInstance); - if (rPerson == nullptr) { - return false; - } - - Person person(FIRST_NAME); - person.updateAddress(); - return (person == *rPerson); - } - - - template<> - const bool person::test_method_updateAddress_const<>(const std::any& pInstance) - { - //instance created via reflection will always hold non-const pointer only. const(or not) is maintained internally to call appropriate method. - Person* rPerson = any_cast(pInstance); - if (rPerson == nullptr) { - return false; - } - - const Person person(FIRST_NAME); - person.updateAddress(); - return (person == *rPerson); - } -} \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE similarity index 93% rename from LICENSE.txt rename to LICENSE index 6337bd93..7dc0e44c 100644 --- a/LICENSE.txt +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2024 Neeraj Singh (neeraj.singh31285@outlook.com) +Copyright (c) 2025 Neeraj Singh Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index 27d0ee54..081a61c0 100644 --- a/README.md +++ b/README.md @@ -1,162 +1,175 @@ -# Reflection Template Library C++ +# Reflection Template Library - Modern C++ Reflection Framework -The **Reflection Template Library for C++** enables introspection of user-defined types, allowing modification of objects at runtime without needing to know their actual types at compile time. +*Reflection Template Library (RTL)* is a lightweight C++ runtime reflection library that enables introspection and dynamic manipulation of ***Types*** — allowing you to access, modify, and invoke objects at runtime without compile-time type knowledge. -Static library, the core design maintains several tables of function pointers(registered by the user) wrapped in lambdas and providing a mechanism to access at runtime. +RTL is implemented as a *static library* that organizes function pointers into `std::vector` tables, with each functor wrapped in a lambda. This design enables constant-time `O(1)` lookups while ensuring type-safe and efficient runtime access. -## Key Features +[![CMake](https://img.shields.io/badge/CMake-Enabled-brightgreen)](https://cmake.org) [![C++20](https://img.shields.io/badge/C++-20-blue)](https://isocpp.org) [![RTL Build](https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP/actions/workflows/build.yml/badge.svg?branch=release)](https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP/actions/workflows/build.yml?query=branch%3Arelease) [![License: MIT](https://img.shields.io/badge/License-MIT-green)](LICENSE) -- **Builder Pattern**: Manual registration of types is simple and intuitive, with no mysterious macros involved. -- **Clean Code**: No reflection-related code needs to be added to class, struct, or function declarations or implementations— keeping your codebase clean and free of clutter. -- **Centralized Registration**: Manage all manual registrations in a single implementation unit, separate from the rest of your project code. -- **Simple Integration**: Just create an instance of `CxxMirror`, pass all type information to reflect as a constructor parameter, and you’re done! - ```c++ - rtl::CxxMirror cxxReflection({/*.. Pass all type information ..*/}); - ``` - The *cxxReflection* object provides interface to query and instantiate registered types. -- **Thread-Safe & Exception-Safe**: The library is designed to be thread-safe and exception-safe, providing error codes on possible failures to ensure robust operation. -- **Automatic Code Generation**: To generate manual registration code automatically, `clang-reflect` can be used. It is a work-in-progress tool available here: *https://github.com/ReflectCxx/clang-reflect*. This tool will generate registration code for any large project without requiring changes to your project’s code. -## How To build (Windows/Linux), +## What RTL Brings to Your Code + +* ***Runtime Reflection for C++*** – Introspect and manipulate objects dynamically, similar to Java or .NET, but with modern C++ idioms. + +* ***Single Source of Truth*** – All metadata lives in one immutable `rtl::CxxMirror`, ensuring a consistent, thread-safe, duplication-free, and deterministic view of reflection data. + +* ***Non-Intrusive & Macro-Free*** – Register reflection metadata externally via a clean builder pattern; no macros, base classes, or global registries. + +* ***Zero-Overhead by Design*** – Metadata is registered and resolved only when used. Reflection introduces no cost beyond the features you explicitly employ. + +* ***Exception-Free Surface*** – All predictable failures return error codes; no hidden throws. + +* ***Deterministic Lifetimes*** – Automatic ownership tracking of `Heap` and `Stack` instances with zero hidden deep copies. + +* ***Cross-Compiler Consistency*** – Pure standard C++20, with no compiler extensions or conditional branching on compiler differences. + +* ***Tooling-Friendly Architecture*** – Reflection data is encapsulated in a single immutable, lazily-initialized object that can be shared with tools and frameworks without compile-time type knowledge — ideal for serializers, debuggers, test frameworks, scripting engines, and editors. + + +[![Design Features](https://img.shields.io/badge/Doc-Design%20Features-blue)](./text-design-docs/DESIGN_PRINCIPLES_AND_FEATURES.md) +[![RTL Syntax & Semantics](https://img.shields.io/badge/Doc-Syntax_&_Semantics-blueviolet)](./text-design-docs/RTL_SYNTAX_AND_SEMANTICS.md) + +## A Quick Preview: Reflection That Looks and Feels Like C++ -Create a build directory in project root folder. -```sh - mkdir build && cd build -``` -Generate a build system using **Unix Makefiles** or **Visual Studio**, in CMake. (Use compiler with C++20) -```sh - cmake -G "" -``` -to build, any IDE applicable to the generator can be used or you can also just build straight from CMake. -```sh - cmake --build . -``` -Run **CxxReflectionTests** binary, generated in ../bin folder. *(tested on windows and Ubuntu-20)* -## How To Use, -In this example, we'll reflect a simple Person class. `Person.h`, ```c++ -class Person { - int age; - std::string name; - -public: - Person(); - Person(std::string, int); - - void setAge(int); - void setName(std::string); - - int getAge() const; - std::string getName() const; -}; +#include "RTLibInterface.h" // Reflection access interface. ``` -### Step 1: Register the Class with 'CxxMirror' -Manually register the class and its members when creating a **`CxxMirror`** object. +Create an instance of `CxxMirror`, passing all type information directly to its constructor — and you're done! ```c++ -#include "CxxMirrorBuilder.h" // Provides registration interface. -#include "Person.h" // User-defined types to be reflected. +auto cxx_mirror = rtl::CxxMirror({ + /* ...register all types here... */ + rtl::type().record("Person").build(), + rtl::type().member().constructor().build(), + rtl::type().member().method("setAge").build(Person::setAge), + rtl::type().member().method("getName").build(Person::getName) +}); +``` -using namespace rtl; +With just this much, you’ve registered your types and unlocked full runtime reflection. The `cxx_mirror` object is your gateway to query, introspect, and instantiate types at runtime — all without compile-time knowledge of those types, without strict static coupling. + +***Without reflection:*** -const CxxMirror& MyReflection() -{ - static const CxxMirror cxxMirror({ - // Register member functions - Reflect().record("Person").method("setAge").build(&Person::setAge), - Reflect().record("Person").method("getAge").build(&Person::getAge), - Reflect().record("Person").method("setName").build(&Person::setName), - Reflect().record("Person").method("getName").build(&Person::getName), - - // Register constructors - Reflect().record("Person").constructor().build(), // Default constructor - Reflect().record("Person").constructor().build() // Constructor with parameters - }); - - return cxxMirror; -} -``` -Registration syntax, ```c++ -Reflect().nameSpace("..") // Optional: specify namespace if the type is enclosed in one. - .record<..>("..") // Register class/struct type (template parameter) and its name (string). - .method("..") // Register function by name. - .build(*); // Pass function pointer. - -Reflect().nameSpace("..") - .record<..>("..") - .constructor<..>() // Register constructor with template parameters as signature. - .build<..>(); // No function pointer needed for constructors. +Person p("John", 42); +p.setAge(43); +std::cout << p.getName(); ``` -### Step 2: Use the 'Person' Class via Reflection -In main.cpp, use the **`Person`** class without directly exposing its type. + +***With reflection:*** + ```c++ -#include "RTLibInterface.h" // Single header including reflection access interface. -extern const rtl::CxxMirror& MyReflection(); +// Look up the class by name +std::optional classPerson = cxx_mirror.getRecord("Person"); -int main() +if (classPerson) // Check has_value() before use. { - // Get 'class Person', Returns 'Record' object associated with 'class Person' - std::optional classPerson = MyReflection().getClass("Person"); - - /* Create an instance of 'class Person' via reflection using the default constructor. - Returns 'RStatus' and 'Instance' objects. - */ auto [status, personObj] = classPerson->instance(); - + // Create a stack-allocated instance. Returns- std::pair + auto [err, robj] = classPerson->create("John", 42); + if (err == rtl::error::None) //Construction successful. + { + // Call setAge(43) on the reflected object + std::optional setAge = classPerson->getMethod("setAge"); + if (setAge) { + // Binds rtl::RObject & rtl::Method, calls with args. + auto [err, ret] = setAge->bind(robj).call(43); //'setAge' is void ('ret' empty). + if (err == rtl::error::None) { /* Operation succeeded. */ } + } + + // Call getName(), which returns std::string + std::optional getName = classPerson->getMethod("getName"); + if (getName) { + //Returns- std::pair + auto [err, ret] = getName->bind(robj).call(); + if (err == rtl::error::None && ret.canViewAs()) + { + std::optional> viewStr = ret.view(); + std::cout << viewStr->get(); // safe. validated above. + } + } + } +} ``` -- `RStatus` provides an error code `(rtl::Error)` that indicates the success or failure of the reflection call, and it also contains the return value (if any) wrapped in `std::any`. -- `Instance` holds the created object (with its type erased), managed on the heap using `std::shared_ptr`. -```c++ +### `Heap` vs `Stack` Allocation and Lifetime Management - /* Create an instance via reflection using a parameterized constructor. - Argument types/order must match else call will fail, returning error-code in 'status'. - */ auto [status, personObj] = classPerson->instance(std::string("John Doe"), int(42)); +RTL lets you create reflected objects on the `Heap` or `Stack` with automatic lifetime management: - // Get method of 'class Person'. Returns a callable 'Method' object. - std::optional setAge = classPerson->getMethod("setAge"); +* Heap (`alloc::Heap`) — objects are owned by an internal `std::unique_ptr` and destroyed when their `rtl::RObject` wrapper goes out of scope. - // Call methods on the 'Person' object. returns 'RStatus'. - RStatus rst = setAge->on(personObj).call(int(42)); - // or with different syntax, - RStatus rst = (*setAge)(personObj)(int(42)); +* Stack (`alloc::Stack`) — independent copies behave like normal stack values and clean up at scope exit. - // Get method of 'class Person' that returns a value. - std::optional getName = classPerson->getMethod("getName"); +* Move semantics — `Heap` objects follow `std::unique_ptr` rules (move transfers ownership, copy/assign disabled). `Stack` objects move like regular values. - // Call method, returns 'RStatus' containing return value. - RStatus retName = getName->on(personObj).call(); - // or with different syntax, - RStatus retName = (*getName)(personObj)(); - - // Extract the return value. - std::string nameStr = std::any_cast(retName.getReturn()); -} -``` -- `std::any_cast` will throw an exception if correct type is not specified. -- Check, `CxxTypeRegistration/src/MyReflection.cpp` for all sort of type registrations. -- Check, `CxxReflectionTests/src` for test cases. +* Return values — All returns are propagated back wrapped in `rtl::RObject`, with temporaries (e.g. smart pointers) cleaned up automatically at scope exit. + +RTL doesn’t invent a new paradigm — it extends C++ itself. You create objects, call methods, and work with types as usual, but now safely at runtime. ## Reflection Features -- ✅ Register and invoke functions, supporting all overloads. -- ✅ Register classes/structs and reflect their methods, constructors, and destructors. -- ✅ Invoke the default constructor. -- ✅ Invoke the copy constructor with a non-const reference argument. -- ✅ Invoke the copy constructor with a const reference argument. -- ✅ Invoke any overloaded constructor. -- ✅ Invoke non-const member functions. -- ✅ Invoke const member functions. -- ✅ Invoke static member functions. -- ✅ Automatically invokes destructor for objects created on the heap via reflection. -- ❌ Reflect properties of classes/structs, providing getter/setter methods. -- ❌ Invoke functions with perfect forwarding. -- ❌ Reflect enums. -- ❌ Reflect classes with composite types that are also reflected. -- ❌ Support single, multiple, multilevel, and virtual inheritance. - -## License -This project is licensed under the MIT License. See the LICENSE file for more details. +* ✅ **Function Reflection** – Register and invoke C-style functions, supporting all kinds of overloads. +* ✅ **Class and Struct Reflection** – Register and dynamically reflect their methods, constructors, and destructors. +* ✅ **Complete Constructor Support** : + * Default construction. + * Copy/Move construction. + * Any overloaded constructor. + +* ✅ **Allocation Strategies & Ownership** : + * Choose between `Heap` or `Stack` allocation. + * Automatic move semantics for ownership transfers. + * Scope-based destruction for `Heap` allocated instances. + +* ✅ **Member Function Invocation** : + * Static methods. + * Const/Non-const methods. + * Any overloaded method, Const/Non-Const based as well. + +* ✅ **Perfect Forwarding** – Binds LValue/RValue to correct overload. +* ✅ **Zero Overhead Forwarding** – No temporaries or copies during method forwarding. +* ✅ **Namespace Support** – Group and reflect under namespaces. +* ✅ **Reflected Returns** – Access return values whose types are unknown at compile time. Validate against the expected type and extract the content safely. +* ✅ **Smart Pointer Reflection** – Reflect `std::shared_ptr` and `std::unique_ptr`, transparently access the underlying type, and benefit from automatic lifetime management with full sharing and cloning semantics. +* 🟨 **Conservative Conversions** – Safely reinterpret reflected values without hidden costs. For example: treat an `int` as a `char`, or a `std::string` as a `std::string_view` / `const char*` — with no hidden copies and only safe, non-widening POD conversions. *(In Progress)* +* 🟨 **Materialize New Types** – Convert a reflected type `A` into type `B` if they are implicitly convertible. Define custom conversions at registration to make them available automatically. *(In Progress)* +* 🚧 **STL Wrapper Support** – Extended support for wrappers like `std::optional` and `std::reference_wrapper`. Return them, forward them as parameters, and access wrapped entities transparently. *(In Progress)* +* 🚧 **Relaxed Argument Matching** – Flexible parameter matching for reflective calls, enabling intuitive conversions and overload resolution. *(In Progress)* +* ❌ **Property Reflection**: Planned. +* ❌ **Enum Reflection**: Planned. +* ❌ **Composite Type Reflection**: Planned. +* ❌ **Inheritance Support**: Planned. + +## How To build (Windows/Linux) + +Create a build directory in the project root folder: + +```sh +mkdir build && cd build +``` + +Generate a build system using **Unix Makefiles** or **Visual Studio** in CMake (use a compiler with C++20): + +```sh +cmake -G "" +``` + +To build, use any IDE applicable to the generator, or build straight from CMake: + +```sh +cmake --build . +``` + +Run the `RTLTestRunApp` or `RTLBenchmarkApp` binaries generated in the `bin/` directory. (Tested with MSVC 19, GCC 14, and Clang 19) +* See `CxxTestRegistration/src/MyReflectionTests/` for introductory examples of type registration and reflective programming. +* See `RTLTestRunApp/src` for detailed test cases. +* See `RTLBenchmarkApp/src` for benchmarking implementations. +* Run `run_benchmarks.sh` to perform automated benchmarking, from micro-level tests to scaled workloads. + ## Contributions -Contributions are welcome! If you find a bug, have a feature request, or want to contribute to the project, feel free to open an issue or submit a pull request on GitHub. + +Contributions welcome! Report bugs, request features, or submit PRs on GitHub. ## Contact -For any questions, suggestions, or feedback, you can reach out via GitHub or email at `neeraj.singh31285@outlook.com`. \ No newline at end of file + +GitHub issues or email at `reflectcxx@outlook.com`. + +## + +***C++ joins the reflection party! — why should Java & .NET have all the fun?*** diff --git a/RTLBenchmarkApp/CMakeLists.txt b/RTLBenchmarkApp/CMakeLists.txt new file mode 100644 index 00000000..ae302c8c --- /dev/null +++ b/RTLBenchmarkApp/CMakeLists.txt @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 3.20) +project(RTLBenchmarkApp LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# =============================== +# Dependencies (Google Benchmark) +# =============================== +include(FetchContent) + +FetchContent_Declare( + benchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG v1.8.3 +) + +# Prevent benchmark from adding extra projects +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) +set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) + +FetchContent_GetProperties(benchmark) +if(NOT benchmark_POPULATED) + FetchContent_Populate(benchmark) + add_subdirectory(${benchmark_SOURCE_DIR} ${benchmark_BINARY_DIR} EXCLUDE_FROM_ALL) +endif() + +# =============================== +# Common Include Paths +# =============================== +set(RTL_INCLUDE_DIRS + inc + "${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/common" + "${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/detail/inc" + "${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/access/inc" + "${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/builder/inc" +) + +# =============================== +# Test Executable +# =============================== +set(CXX_EXE_NAME RTLBenchmarkApp) + +# =============================== +# Benchmarks (only target) +# =============================== +add_executable(${CXX_EXE_NAME} + src/main.cpp + src/BenchMark.h + src/BenchMark.cpp + src/StandardCall.h + src/StandardCall.cpp + src/StdFunction.cpp + src/ReflectedCall.h + src/ReflectedCall.cpp +) + + +target_include_directories(${CXX_EXE_NAME} PRIVATE ${RTL_INCLUDE_DIRS}) + +target_link_libraries(${CXX_EXE_NAME} + PRIVATE + benchmark + ReflectionTemplateLib +) \ No newline at end of file diff --git a/RTLBenchmarkApp/src/BenchMark.cpp b/RTLBenchmarkApp/src/BenchMark.cpp new file mode 100644 index 00000000..72b66cc6 --- /dev/null +++ b/RTLBenchmarkApp/src/BenchMark.cpp @@ -0,0 +1,74 @@ + + +#include +#include +#include + +#include "BenchMark.h" +#include "RTLibInterface.h" + + +namespace bm +{ + std::size_t g_work_load = 0; + + std::optional g_work_done = std::string(); + + extern std::string perform_work(const argStr_t& pMsg); +} + + +namespace bm +{ + void sendMessage(argStr_t pMsg) + { + if(g_work_load){ + g_work_done = perform_work(pMsg); + } + } + + void Node::sendMessage(argStr_t pMsg) + { + if(g_work_load){ + g_work_done = perform_work(pMsg); + } + } + + retStr_t getMessage(argStr_t pMsg) + { + if(g_work_load){ + g_work_done = perform_work(pMsg); + } + return retStr_t(g_work_done->c_str()); + } + + retStr_t Node::getMessage(argStr_t pMsg) + { + if(g_work_load){ + g_work_done = perform_work(pMsg); + } + return retStr_t(g_work_done->c_str()); + } +} + + +namespace cxx +{ + const rtl::CxxMirror& mirror() + { + static auto cxx_mirror = rtl::CxxMirror({ + + rtl::type().function("getMessage").build(bm::getMessage), + + rtl::type().function("sendMessage").build(bm::sendMessage), + + rtl::type().record("Node").build(), + + rtl::type().member().method("sendMessage").build(&bm::Node::sendMessage), + + rtl::type().member().method("getMessage").build(&bm::Node::getMessage) + }); + + return cxx_mirror; + } +} \ No newline at end of file diff --git a/RTLBenchmarkApp/src/BenchMark.h b/RTLBenchmarkApp/src/BenchMark.h new file mode 100644 index 00000000..ac27f784 --- /dev/null +++ b/RTLBenchmarkApp/src/BenchMark.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace bm +{ + using argStr_t = std::string_view; + using retStr_t = std::string_view; + + struct Node + { + void sendMessage(argStr_t); + retStr_t getMessage(argStr_t); + }; +} + + +namespace bm +{ + static argStr_t g_longStr = "Lorem ipsum" + "dolor sit amet, consectetur adipiscing elit, sed do" + "do aeiusmod tempor incididunt uth labore et dolore magna aliqua. Ut enim ad minim veniam, quis" + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure" + "dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Except" + "eur ssint occaecat cupidatat nnon proident, sunt in culpa qui officia deserunt mollit anim id" + "Lorem ipsum dolor sit amet laboris nisi ut aliquip ex ea commodo"; +} \ No newline at end of file diff --git a/RTLBenchmarkApp/src/ReflectedCall.cpp b/RTLBenchmarkApp/src/ReflectedCall.cpp new file mode 100644 index 00000000..325c72ea --- /dev/null +++ b/RTLBenchmarkApp/src/ReflectedCall.cpp @@ -0,0 +1,119 @@ + +#include + +#include "ReflectedCall.h" +#include "RTLibInterface.h" +#include "BenchMark.h" + +namespace cxx +{ + extern const rtl::CxxMirror& mirror(); +} + +namespace +{ + static rtl::Function GetMessage = cxx::mirror().getFunction("getMessage").value(); + static rtl::Function SendMessage = cxx::mirror().getFunction("sendMessage").value(); + + static rtl::Method NodeGetMessage = cxx::mirror().getRecord("Node")->getMethod("getMessage").value(); + static rtl::Method NodeSendMessage = cxx::mirror().getRecord("Node")->getMethod("sendMessage").value(); + + static rtl::RObject nodeObj = []() + { + auto Node = cxx::mirror().getRecord("Node").value(); + auto [err, robj] = Node.create(); + if (nodeObj.isEmpty()) { + std::cout << "[0] nodeObj empty! \n"; + } + return std::move(robj); + }(); +} + + + namespace + { + static auto _test0 = []() + { + auto err = SendMessage(bm::g_longStr).err; + + if (err != rtl::error::None) { + std::cout << "[1] error: "<< rtl::to_string(err)<<"\n"; + } + return 0; + }; + + static auto _test1 = []() + { + auto err = NodeSendMessage.bind(nodeObj).call(bm::g_longStr).err; + + if (err != rtl::error::None) { + std::cout << "[2] error: " << rtl::to_string(err) << "\n"; + } + return 0; + }; + + static auto _test2 = []() + { + auto err = GetMessage(bm::g_longStr).err; + + if (err != rtl::error::None) { + std::cout << "[3] error: " << rtl::to_string(err) << "\n"; + } + return 0; + }; + + static auto _test3 = []() + { + auto err = NodeGetMessage(nodeObj)(bm::g_longStr).err; + + if (err != rtl::error::None) { + std::cout << "[4] error: " << rtl::to_string(err) << "\n"; + } + return 0; + }; +} + + + +void ReflectedCall::set(benchmark::State& state) +{ + static auto _=_test0(); + for (auto _: state) { + + auto error = SendMessage(bm::g_longStr).err; + benchmark::DoNotOptimize(error); + } +} + + +void ReflectedCall::get(benchmark::State& state) +{ + static auto _=_test2(); + for (auto _: state) + { + auto error = GetMessage(bm::g_longStr).err; + benchmark::DoNotOptimize(error); + } +} + + +void ReflectedMethodCall::set(benchmark::State& state) +{ + static auto _=_test1(); + for (auto _: state) + { + auto error = NodeSendMessage(nodeObj)(bm::g_longStr).err; + benchmark::DoNotOptimize(error); + } +} + + +void ReflectedMethodCall::get(benchmark::State& state) +{ + static auto _=_test3(); + for (auto _: state) + { + auto error = NodeGetMessage(nodeObj)(bm::g_longStr).err; + benchmark::DoNotOptimize(error); + } +} \ No newline at end of file diff --git a/RTLBenchmarkApp/src/ReflectedCall.h b/RTLBenchmarkApp/src/ReflectedCall.h new file mode 100644 index 00000000..59416ca2 --- /dev/null +++ b/RTLBenchmarkApp/src/ReflectedCall.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +struct ReflectedCall +{ + static void set(benchmark::State& state); + + static void get(benchmark::State& state); +}; + + +struct ReflectedMethodCall +{ + static void set(benchmark::State& state); + + static void get(benchmark::State& state); +}; \ No newline at end of file diff --git a/RTLBenchmarkApp/src/StandardCall.cpp b/RTLBenchmarkApp/src/StandardCall.cpp new file mode 100644 index 00000000..39dda878 --- /dev/null +++ b/RTLBenchmarkApp/src/StandardCall.cpp @@ -0,0 +1,101 @@ + +#include +#include +#include + +#include "BenchMark.h" +#include "StandardCall.h" + +namespace +{ + static auto _put_line = []() { + std::cout << "-------------------------------------" + "-------------------------------------" << std::endl; + return 0; + }; + + static auto _new_line = []() { + std::cout << std::endl; + return 0; + }; +} + + +namespace bm +{ + extern void sendMessage(argStr_t); + + extern retStr_t getMessage(argStr_t); + + extern std::optional g_work_done; + + extern std::function SendMessage; + + extern std::function NodeSendMessage; + + extern std::function GetMessage; + + extern std::function NodeGetMessage; +} + + +void NativeCall::set(benchmark::State& state) +{ + for (auto _: state) + { + bm::sendMessage(bm::g_longStr); + benchmark::DoNotOptimize(bm::g_work_done->c_str()); + } +} + + +void NativeCall::get(benchmark::State& state) +{ + static auto _=_put_line(); + for (auto _: state) + { + benchmark::DoNotOptimize(bm::getMessage(bm::g_longStr)); + } +} + + +void StdFuncCall::set(benchmark::State& state) +{ + static auto _=_new_line(); + for (auto _: state) + { + bm::SendMessage(bm::g_longStr); + benchmark::DoNotOptimize(bm::g_work_done->c_str()); + } +} + + +void StdFuncMethodCall::set(benchmark::State& state) +{ + static auto _=_new_line(); + for (auto _: state) + { + bm::NodeSendMessage(bm::g_longStr); + benchmark::DoNotOptimize(bm::g_work_done->c_str()); + } +} + + +void StdFuncCall::get(benchmark::State& state) +{ + static auto _=_new_line(); + for (auto _: state) + { + benchmark::DoNotOptimize(bm::GetMessage(bm::g_longStr)); + } +} + + +void StdFuncMethodCall::get(benchmark::State& state) +{ + static auto _=_new_line(); + for (auto _: state) + { + benchmark::DoNotOptimize(bm::NodeGetMessage(bm::g_longStr)); + } +} \ No newline at end of file diff --git a/RTLBenchmarkApp/src/StandardCall.h b/RTLBenchmarkApp/src/StandardCall.h new file mode 100644 index 00000000..da2e4a45 --- /dev/null +++ b/RTLBenchmarkApp/src/StandardCall.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +struct NativeCall +{ + static void set(benchmark::State& state); + + static void get(benchmark::State& state); +}; + + +struct StdFuncCall +{ + static void set(benchmark::State& state); + + static void get(benchmark::State& state); +}; + + +struct StdFuncMethodCall +{ + static void set(benchmark::State& state); + + static void get(benchmark::State& state); +}; \ No newline at end of file diff --git a/RTLBenchmarkApp/src/StdFunction.cpp b/RTLBenchmarkApp/src/StdFunction.cpp new file mode 100644 index 00000000..b7c170e3 --- /dev/null +++ b/RTLBenchmarkApp/src/StdFunction.cpp @@ -0,0 +1,49 @@ + +#include +#include "BenchMark.h" + +namespace bm +{ + extern std::size_t g_work_load; + std::string perform_work(const bm::argStr_t& pMsg) + { + auto workStr = std::string(); + for(int i = 0; i < bm::g_work_load; ++i) + { + workStr += pMsg; + } + return workStr; + } +} + + +namespace bm +{ + static Node node; + + extern void sendMessage(argStr_t); + + extern retStr_t getMessage(argStr_t); + + std::function SendMessage = [](argStr_t& pMsg) + { + bm::sendMessage(pMsg); + }; + + std::function NodeSendMessage = [](bm::argStr_t& pMsg) + { + node.sendMessage(pMsg); + }; + + std::function GetMessage = [](bm::argStr_t& pMsg) + { + auto retMsg = bm::getMessage(pMsg); + return retMsg; + }; + + std::function NodeGetMessage = [](bm::argStr_t& pMsg) + { + auto retMsg = node.getMessage(pMsg); + return retMsg; + }; +} diff --git a/RTLBenchmarkApp/src/main.cpp b/RTLBenchmarkApp/src/main.cpp new file mode 100644 index 00000000..bcefd8d1 --- /dev/null +++ b/RTLBenchmarkApp/src/main.cpp @@ -0,0 +1,49 @@ + +#include +#include +#include + +#include "StandardCall.h" +#include "ReflectedCall.h" + +BENCHMARK(NativeCall::set); + +BENCHMARK(StdFuncCall::set); +BENCHMARK(ReflectedCall::set); + +BENCHMARK(StdFuncMethodCall::set); +BENCHMARK(ReflectedMethodCall::set); + +BENCHMARK(NativeCall::get); + +BENCHMARK(StdFuncCall::get); +BENCHMARK(ReflectedCall::get); + +BENCHMARK(StdFuncMethodCall::get); +BENCHMARK(ReflectedMethodCall::get); + +namespace bm +{ + extern std::size_t g_work_load; +} + +int main(int argc, char** argv) +{ + if (argc > 1) + { + bm::g_work_load = std::stoi(argv[1]); + for (int i = 1; i < argc - 1; ++i) { + argv[i] = argv[i + 1]; + } + --argc; + + std::cout << "\n======== RTL Benchmark Configuration ========\n" + << "Workload: concatenate string of length 500\n" + << "Scale : " << bm::g_work_load << " iterations\n" + << "=============================================\n\n"; + } + + ::benchmark::Initialize(&argc, argv); + if (::benchmark::ReportUnrecognizedArguments(argc, argv)) return 1; + ::benchmark::RunSpecifiedBenchmarks(); +} diff --git a/RTLTestRunApp/CMakeLists.txt b/RTLTestRunApp/CMakeLists.txt new file mode 100644 index 00000000..3b2177eb --- /dev/null +++ b/RTLTestRunApp/CMakeLists.txt @@ -0,0 +1,63 @@ +cmake_minimum_required(VERSION 3.20) +project(RTLTestRunApp LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# =============================== +# Dependencies (GoogleTest) +# =============================== +include(FetchContent) + +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip +) + +# Prevent GTest from overriding CRT settings on Windows +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +# Avoid GTest adding its own tests into the solution +set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) +set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) +set(BUILD_GTEST ON CACHE BOOL "" FORCE) + +FetchContent_MakeAvailable(googletest) + +# =============================== +# Common Include Paths +# =============================== +set(RTL_INCLUDE_DIRS + inc + "${CMAKE_SOURCE_DIR}/CxxTestUtils/inc" + "${CMAKE_SOURCE_DIR}/CxxTestRegistration/inc" + "${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/common" + "${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/detail/inc" + "${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/access/inc" + "${CMAKE_SOURCE_DIR}/ReflectionTemplateLib/builder/inc" +) + +# =============================== +# Test Executable +# =============================== +set(CXX_EXE_NAME RTLTestRunApp) + +# Add all test sources (either glob or include your src/CMakeLists.txt) +file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS src/*.cpp) +add_executable(${CXX_EXE_NAME} ${TEST_SOURCES}) + +target_include_directories(${CXX_EXE_NAME} PRIVATE ${RTL_INCLUDE_DIRS}) + +target_link_libraries(${CXX_EXE_NAME} + PRIVATE + CxxTestUtils + ReflectionTemplateLib + CxxTestRegistration + GTest::gtest_main +) + +# =============================== +# GoogleTest Integration +# =============================== +include(GoogleTest) +gtest_discover_tests(${CXX_EXE_NAME}) diff --git a/RTLTestRunApp/src/CMakeLists.txt b/RTLTestRunApp/src/CMakeLists.txt new file mode 100644 index 00000000..e5d3fc3f --- /dev/null +++ b/RTLTestRunApp/src/CMakeLists.txt @@ -0,0 +1,59 @@ +# CMakeLists.txt for CxxReflectionTests +cmake_minimum_required(VERSION 3.20) + +project(RTLTestRunApp) + +# Create a variable containing the source files for your target +set(LOCAL_SOURCES_0 + "${CMAKE_CURRENT_LIST_DIR}/FunctionalityTests/ClassMethodsTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/FunctionalityTests/ConstMethodOverloadTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/FunctionalityTests/ConstructorTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/FunctionalityTests/CopyConstructorTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/FunctionalityTests/NameSpaceGlobalsTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/FunctionalityTests/ReflectionOpErrorCodeTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/FunctionalityTests/StaticMethodTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/FunctionalityTests/PerfectForwardingTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/FunctionalityTests/MoveConstructorTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/FunctionalityTests/ReturnValueReflectionTest.cpp" +) + +# Create a variable containing the source files for your target +set(LOCAL_ROBJECT + "${CMAKE_CURRENT_LIST_DIR}/RObjectTests/RObjectReflecting_bool.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RObjectTests/RObjectReflecting_char.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RObjectTests/RObjectReflecting_strings.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RObjectTests/RObjectReflecting_int.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RObjectTests/RObjectReflecting_arrays.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RObjectTests/RObjectReflecting_stdUniquePtr.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RObjectTests/RObjectReflecting_stdSharedPtr.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RObjectTests/RObjectImplicitConversions.cpp" +) + +set(LOCAL_MY_REFLECTION + "${CMAKE_CURRENT_LIST_DIR}/MyReflectionTests/MyReflectingType.h" + "${CMAKE_CURRENT_LIST_DIR}/MyReflectionTests/MyReflectionTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/MyReflectionTests/MyCxxTestRegistration.cpp" +) + + +set(LOCAL_CXXMIRROR + "${CMAKE_CURRENT_LIST_DIR}/CxxMirrorTests/CxxMirrorObjectTest.cpp" + "${CMAKE_CURRENT_LIST_DIR}/CxxMirrorTests/CxxMirrorThreadingTest.h" + "${CMAKE_CURRENT_LIST_DIR}/CxxMirrorTests/CxxMirrorThreadingTest.cpp" +) + + +# Add any additional source files if needed +target_sources(RTLTestRunApp + PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/main.cpp" + "${LOCAL_SOURCES_0}" + "${LOCAL_ROBJECT}" + "${LOCAL_CXXMIRROR}" + "${LOCAL_MY_REFLECTION}" +) + +SOURCE_GROUP("Source Files\\FunctionalityTests" FILES ${LOCAL_SOURCES_0}) +SOURCE_GROUP("Source Files\\RObjectTests" FILES ${LOCAL_ROBJECT}) +SOURCE_GROUP("Source Files\\CxxMirrorTests" FILES ${LOCAL_CXXMIRROR}) +SOURCE_GROUP("Source Files\\MyReflectionTests" FILES ${LOCAL_MY_REFLECTION}) \ No newline at end of file diff --git a/RTLTestRunApp/src/CxxMirrorTests/CxxMirrorObjectTest.cpp b/RTLTestRunApp/src/CxxMirrorTests/CxxMirrorObjectTest.cpp new file mode 100644 index 00000000..d98723d4 --- /dev/null +++ b/RTLTestRunApp/src/CxxMirrorTests/CxxMirrorObjectTest.cpp @@ -0,0 +1,358 @@ + +#include + +#include + +#include "RTLibInterface.h" +#include "CxxMirrorToJson.h" + +namespace +{ + const rtl::CxxMirror cxx_mirror() + { + return rtl::CxxMirror({ + + // Register char as a record type (fundamental but instantiable). + rtl::type().record("char").build(), + + // Register strlen as a global function (C library function). + rtl::type().function("strlen").build(strlen), + + // Register member function push_back(const int&) for std::vector. + // Demonstrates overload resolution via explicit signature selection. + rtl::type().member>().method("push_back").build(&std::vector::push_back), + + // Register const-qualified method empty() for std::vector. + // RTL enforces const-correctness by separating methodConst. + rtl::type().member>().methodConst("empty").build(&std::vector::empty), + + // Register std::vector itself as a record with name "vector_int". + rtl::type().record>("vector_int").build(), + + // Register strlen again, redundant and gets ignored. + rtl::type().function("strlen").build(std::strlen) + }); + } +} + + +namespace rtl_tests +{ + + TEST(CxxMirrorObjectTest, multiple_initializations_same_set__with_std_vector) + { + std::string mirrorStr0; + { + // 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; + mirrorStr0 = rtl::CxxMirrorToJson::toJson(mirror0); + } + std::string mirrorStr1; + { + // Freshly constructed mirror should serialize identically to previous one. + mirrorStr1 = rtl::CxxMirrorToJson::toJson(cxx_mirror()); + } + EXPECT_EQ(mirrorStr0, mirrorStr1); + + // Retrieve the reflected record for std::vector. + std::optional classVectorInt = cxx_mirror().getRecord("vector_int"); + ASSERT_TRUE(classVectorInt); + + // Create an instance of std::vector on the stack via RTL. + // Uses RObject with stack lifetime. + auto [err, robj] = classVectorInt->create(); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(robj.isEmpty()); + + { + // Lookup the const method empty() in std::vector. + std::optional isEmpty = classVectorInt->getMethod("empty"); + ASSERT_TRUE(isEmpty); + + // Bind the reflected method to the object and call it. + // Exception-free API: returns error code + result object. + auto [err, ret] = isEmpty->bind(robj).call(); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(ret.isEmpty()); + + // Safe typed access to return value via rtl::view. + std::optional> rview = ret.view(); + ASSERT_TRUE(rview); + EXPECT_TRUE(rview->get()); // Newly created vector should be empty. + } + + // Prepare a native vector with values to push. + std::vector intArr0 = { 1565, 7271, 4357 }; + { + // Lookup push_back method and call it multiple times with different values. + std::optional push = classVectorInt->getMethod("push_back"); + ASSERT_TRUE(push); + { + auto [err, ret] = push->bind(robj).call(intArr0[0]); + EXPECT_TRUE(err == rtl::error::None); + } { + auto [err, ret] = push->bind(robj).call(intArr0[1]); + EXPECT_TRUE(err == rtl::error::None); + } { + auto [err, ret] = push->bind(robj).call(intArr0[2]); + EXPECT_TRUE(err == rtl::error::None); + } + } + + // Verify that reflected object can be safely reinterpreted as std::vector. + EXPECT_TRUE(robj.canViewAs>()); + + // Access internal vector instance safely via rtl::view. + std::optional>> vecView = robj.view>(); + ASSERT_TRUE(vecView); + + // Get reference to actual underlying vector and compare with expected values. + auto& intArr1 = vecView->get(); + EXPECT_EQ(intArr0, intArr1); + } + + + // This test demonstrates redundant function registration handling + // and argument forwarding quirks for C-style strings. + TEST(CxxMirrorObjectTest, rednudent_registration__std_cstring_function) + { + auto cxxMirror = rtl::CxxMirror({ + + // Redundent registrations + rtl::type().function("strlen").build(std::strlen), + rtl::type().function("strlen").build(std::strlen) + +/* emits warning on console - + [WARNING] Multiple registrations of the same function-pointer detected. + function-pointer already registered as "strlen" + This registration is ignored. */ + }); + + std::optional cstrLen = cxxMirror.getFunction("strlen"); + ASSERT_TRUE(cstrLen); + + { + // Case 1: normal pointer (deduces as 'const char*') + const char* cstr = "Reflection Template Library C++"; + + auto [err, ret] = cstrLen->bind().call(cstr); + ASSERT_TRUE(err == rtl::error::None); + + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + std::optional> rview = ret.view(); + ASSERT_TRUE(rview); + + std::size_t rlen = rview->get(); + std::size_t clen = std::strlen(cstr); + EXPECT_EQ(rlen, clen); + } { + // Case 2: constexpr top-level const (deduces as 'const char* const&') + constexpr const char* cstr = "Reflection Template Library C++"; + + // Need to forward as 'const char*' + auto [err, ret] = cstrLen->bind().call(cstr); + ASSERT_TRUE(err == rtl::error::None); + + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + std::optional> rview = ret.view(); + ASSERT_TRUE(rview); + + std::size_t rlen = rview->get(); + std::size_t clen = std::strlen(cstr); + EXPECT_EQ(rlen, clen); + } { + // Case 3: string literal (deduces as const char[N], here const char[32]) + // Must explicitly forward as 'const char*'. + auto [err, ret] = cstrLen->bind().call("Reflection Template Library C++"); + ASSERT_TRUE(err == rtl::error::None); + + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + std::optional> rview = ret.view(); + ASSERT_TRUE(rview); + + std::size_t rlen = rview->get(); + std::size_t clen = std::strlen("Reflection Template Library C++"); + EXPECT_EQ(rlen, clen); + } + } + + + TEST(CxxMirrorObjectTest, redundent_registration__std_cstring_func_with_global_cstring) + { + auto cxxMirror = rtl::CxxMirror({ + + // Register strlen (C function) under the name "strlen". + // RTL tracks uniqueness of function-pointer registrations. + rtl::type().function("strlen").build(strlen), + + // Attempt to register the same function-pointer again. + // RTL emits a warning and ignores this redundant registration, + // ensuring stable, non-ambiguous metadata. + rtl::type().function("strlen").build(std::strlen) + + /* Console output: + [WARNING] Multiple registrations of the same function-pointer detected. + function-pointer already registered as "strlen" + This registration is ignored. + */ + }); + + // Retrieve the reflected function "strlen" from the mirror. + std::optional cstrLen = cxxMirror.getFunction("strlen"); + ASSERT_TRUE(cstrLen); + + // Prepare a C-style string for testing. + const char* cstr = "Modern C++ Reflection Framework"; + + // Bind the reflected strlen and call it with cstr. + // RTL returns error code + result object instead of exceptions. + auto [err, ret] = cstrLen->bind().call(cstr); + + ASSERT_TRUE(err == rtl::error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + // Safely extract the return value as size_t via typed view. + std::optional> rview = ret.view(); + ASSERT_TRUE(rview); + + std::size_t rlen = rview->get(); + std::size_t clen = std::strlen(cstr); + + // Verify RTL-reflected call matches native call. + EXPECT_EQ(rlen, clen); + } + + + + TEST(CxxMirrorObjectTest, redundent_regis_with_namespace__std_cstring_func_with_global_cstring) + { + auto cxxMirror = rtl::CxxMirror({ + // Redundant registrations with different namespaces. + // No warning is emitted, because they produce different rtl::Function entries + // (one global, one inside namespace "std"). + // Both functions wrap the same function-pointer, so their FunctorIds match. + rtl::type().function("strlen").build(strlen), + rtl::type().ns("std").function("strlen").build(std::strlen) + }); + + // Lookup global function "strlen". + std::optional cstrLen = cxxMirror.getFunction("strlen"); + ASSERT_TRUE(cstrLen); + { + const char* cstr = "Modern C++ Reflection Framework"; + + // Call the reflected global strlen. + auto [err, ret] = cstrLen->bind().call(cstr); + ASSERT_TRUE(err == rtl::error::None); + + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + std::optional> rview = ret.view(); + ASSERT_TRUE(rview); + + std::size_t rlen = rview->get(); + std::size_t clen = strlen(cstr); + EXPECT_EQ(rlen, clen); + } + + // Lookup namespaced function "std::strlen". + std::optional stdStrLen = cxxMirror.getFunction("std", "strlen"); + ASSERT_TRUE(stdStrLen); + { + const char* cstr = "Modern C++ Reflection Framework"; + + // Call the reflected std::strlen. + auto [err, ret] = stdStrLen->bind().call(cstr); + ASSERT_TRUE(err == rtl::error::None); + + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + std::optional> rview = ret.view(); + ASSERT_TRUE(rview); + + std::size_t rlen = rview->get(); + std::size_t clen = std::strlen(cstr); + EXPECT_EQ(rlen, clen); + } + + // Even though the functions are registered in different namespaces, + // the underlying FunctorIds (which identify function-pointers) must be equal. + const std::vector& cfunctorIds = cstrLen->getFunctors(); + const std::vector& stdfunctorIds = stdStrLen->getFunctors(); + + EXPECT_EQ(cfunctorIds, stdfunctorIds); + } + + + + TEST(CxxMirrorObjectTest, redundent_regis_with_different_names__std_cstring_func_with_global_cstring) + { + auto cxxMirror = rtl::CxxMirror({ + // Redundant registrations with different symbolic names. + // No warning is emitted, since each rtl::Function has a distinct name. + // Both map to the same underlying function-pointer, so FunctorIds match. + rtl::type().function("cStrlen").build(strlen), + rtl::type().function("stdStrlen").build(std::strlen) + }); + + // Lookup function registered as "cStrlen". + std::optional cstrLen = cxxMirror.getFunction("cStrlen"); + ASSERT_TRUE(cstrLen); + { + const char* cstr = "Modern C++ Reflection Framework"; + + // Call reflected cStrlen. + auto [err, ret] = cstrLen->bind().call(cstr); + ASSERT_TRUE(err == rtl::error::None); + + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + std::optional> rview = ret.view(); + ASSERT_TRUE(rview); + + std::size_t rlen = rview->get(); + std::size_t clen = strlen(cstr); + EXPECT_EQ(rlen, clen); + } + + // Lookup function registered as "stdStrlen". + std::optional stdStrLen = cxxMirror.getFunction("stdStrlen"); + ASSERT_TRUE(stdStrLen); + { + const char* cstr = "Modern C++ Reflection Framework"; + + // Call reflected stdStrlen. + auto [err, ret] = stdStrLen->bind().call(cstr); + ASSERT_TRUE(err == rtl::error::None); + + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + std::optional> rview = ret.view(); + ASSERT_TRUE(rview); + + std::size_t rlen = rview->get(); + std::size_t clen = std::strlen(cstr); + EXPECT_EQ(rlen, clen); + } + + // Despite different symbolic names, both reflect the same function-pointer. + // Hence, their FunctorIds must be identical. + const std::vector& cfunctorIds = cstrLen->getFunctors(); + const std::vector& stdfunctorIds = stdStrLen->getFunctors(); + + EXPECT_EQ(cfunctorIds, stdfunctorIds); + } +} diff --git a/RTLTestRunApp/src/CxxMirrorTests/CxxMirrorThreadingTest.cpp b/RTLTestRunApp/src/CxxMirrorTests/CxxMirrorThreadingTest.cpp new file mode 100644 index 00000000..b340a9c0 --- /dev/null +++ b/RTLTestRunApp/src/CxxMirrorTests/CxxMirrorThreadingTest.cpp @@ -0,0 +1,308 @@ + +#include +#include +#include +#include + +#include "../../CxxTestProps/inc/Date.h" +#include "../../CxxTestProps/inc/Book.h" +#include "../../CxxTestProps/inc/Animal.h" +#include "../../CxxTestProps/inc/Person.h" +#include "../../CxxTestProps/inc/Library.h" +#include "../../CxxTestProps/inc/Complex.h" +#include "../MyReflectionTests/MyReflectingType.h" + +#include "TestUtilsBook.h" +#include "TestUtilsDate.h" +#include "TestUtilsPerson.h" +#include "TestUtilsAnimal.h" +#include "GlobalTestUtils.h" + +#include "RTLibInterface.h" +#include "CxxMirrorThreadingTest.h" + +using namespace test_utils; + +namespace rtl_tests +{ + void InitMirror::reflectingEvent() + { + auto _ = rtl::CxxMirror({ + + rtl::type().ns(event::ns).record(event::struct_).build(), + + rtl::type().member().method(event::str_reset).build(&nsdate::Event::reset), + }); + + std::cout << "\n [t2]\trtl_tests::InitMirror::reflectingEvent() ==> Done.\n"; + } + + + void InitMirror::reflectingLibrary() + { + auto _ = rtl::CxxMirror({ + + rtl::type().record(library::class_).build(), + + rtl::type().member().methodStatic(library::str_addBook).build(&Library::addBook), + + rtl::type().member().methodStatic(library::str_getBookByTitle).build(&Library::getBookByTitle) + }); + + std::cout << "\n [t5]\trtl_tests::InitMirror::reflectingLibrary() ==> Done.\n"; + } + + + void InitMirror::reflectingDate() + { + auto _ = rtl::CxxMirror({ + + rtl::type().ns(date::ns).record(date::struct_).build(), + + rtl::type().member().constructor().build(), + + rtl::type().member().constructor().build(), + + rtl::type().member().method(date::str_updateDate).build(&nsdate::Date::updateDate), + + rtl::type().member().methodConst(date::str_getAsString).build(&nsdate::Date::getAsString) + }); + + std::cout << "\n [t1]\trtl_tests::InitMirror::reflectingDate() ==> Done.\n"; + } + + + void InitMirror::reflectingCalender() + { + auto _ = rtl::CxxMirror({ + + rtl::type().ns(date::ns).record(calender::struct_).build(), + + rtl::type().member().methodStatic(calender::str_create).build(&nsdate::Calender::create), + + rtl::type().member().method(calender::str_getTheEvent).build(&nsdate::Calender::getTheEvent), + + rtl::type().member().method(calender::str_getTheDate).build(&nsdate::Calender::getTheDate), + + rtl::type().member().method(calender::str_getSavedEvent).build(&nsdate::Calender::getSavedEvent), + + rtl::type().member().method(calender::str_getSavedDate).build(&nsdate::Calender::getSavedDate) + }); + + std::cout << "\n [t7]\trtl_tests::InitMirror::reflectingCalender() ==> Done.\n"; + } + + + void InitMirror::reflectingPodsStl() + { + auto _ = rtl::CxxMirror({ + + rtl::type().function("strlen").build(std::strlen), + + rtl::type().record("char").build(), + + rtl::type().record>("vector_int").build(), + + rtl::type().ns("std").record("string").build(), + + rtl::type().ns("std").record("string_view").build(), + + rtl::type().member().methodConst("empty").build(&std::string::empty), + + rtl::type().member().methodConst("empty").build(&std::string_view::empty), + + rtl::type().member>().methodConst("empty").build(&std::vector::empty), + + rtl::type().member>().method("push_back").build(&std::vector::push_back) + }); + + std::cout << "\n [t6]\trtl_tests::InitMirror::reflectingPodsStl() ==> Done.\n"; + } + + + void InitMirror::reflectingCStyleFunctions() + { + auto _ = rtl::CxxMirror({ + + rtl::type().function(str_reverseString).build(reverseString), + + rtl::type().function(str_reverseString).build(reverseString), + + rtl::type().function(str_reverseString).build(reverseString), + + rtl::type().function(str_getComplexNumAsString).build(getComplexNumAsString), + + rtl::type().ns(str_complex).function(str_setReal).build(complex::setReal), + + rtl::type().ns(str_complex).function(str_setImaginary).build(complex::setImaginary), + + rtl::type().ns(str_complex).function(str_getMagnitude).build(complex::getMagnitude), + + rtl::type().ns("ext").function("sendString").build(my_type::ext::sendString), + + rtl::type().ns("ext").function("sendAsString").build(my_type::ext::sendAsString), + + rtl::type().ns("ext").function("sendAsString").build(my_type::ext::sendAsString), + + rtl::type().ns("ext").function("sendAsString").build(my_type::ext::sendAsString) + }); + + std::cout << "\n [t9]\trtl_tests::InitMirror::reflectingCStyleFunctions() ==> Done.\n"; + } + + + void InitMirror::reflectingBook() + { + auto _ = rtl::CxxMirror({ + + rtl::type().record(book::class_).build(), + + rtl::type().member().constructor().build(), + + rtl::type().member().method(book::str_setAuthor).build(&Book::setAuthor), + + rtl::type().member().method(book::str_addPreface).build(&Book::addPreface), + + rtl::type().member().method(book::str_setDescription).build(&Book::setDescription), + + rtl::type().member().method(book::str_getPublishedOn).build(&Book::getPublishedOn), + + rtl::type().member().method(book::str_addCopyrightTag).build(&Book::addCopyrightTag), + + rtl::type().member().method(book::str_updateBookInfo).build(&Book::updateBookInfo), + + rtl::type().member().method(book::str_updateBookInfo).build(&Book::updateBookInfo), + + rtl::type().member().method(book::str_updateBookInfo).build(&Book::updateBookInfo) + }); + + std::cout << "\n [t0]\trtl_tests::InitMirror::reflectingBook() ==> Done.\n"; + } + + + void InitMirror::reflectingMyTypePerson() + { + auto _ = rtl::CxxMirror({ + + rtl::type().record("Person").build(), + + rtl::type().member().constructor().build(), + + rtl::type().member().method("getName").build(&my_type::Person::getName), + + rtl::type().member().methodStatic("getDefaults").build(&my_type::Person::getDefaults), + + rtl::type().member().method("updateAddress").build(&my_type::Person::updateAddress), + + rtl::type().member().methodConst("updateAddress").build(&my_type::Person::updateAddress), + + rtl::type().member().method("setTitle").build(&my_type::Person::setTitle), + + rtl::type().member().method("setOccupation").build(&my_type::Person::setOccupation), + + rtl::type().member().method("setOccupation").build(&my_type::Person::setOccupation), + + rtl::type().member().method("setProfile").build(&my_type::Person::setProfile), + + rtl::type().member().method("setProfile").build(&my_type::Person::setProfile), + + rtl::type().member().methodConst("getProfile").build(&my_type::Person::getProfile) + }); + + std::cout << "\n [t8]\trtl_tests::InitMirror::reflectingMyTypePerson() ==> Done.\n"; + } + + + void InitMirror::reflectingPerson() + { + auto _ = rtl::CxxMirror({ + + rtl::type().record(person::class_).build(), + + rtl::type().member().constructor().build(), + + rtl::type().member().methodStatic(person::str_createPtr).build(&Person::createPtr), + + rtl::type().member().method(person::str_updateAddress).build(&Person::updateAddress), + + rtl::type().member().method(person::str_updateAddress).build(&Person::updateAddress), + + rtl::type().member().method(person::str_getFirstName).build(&Person::getFirstName), + + rtl::type().member().methodConst(person::str_updateLastName).build(&Person::updateLastName), + + rtl::type().member().methodConst(person::str_updateAddress).build(&Person::updateAddress), + + rtl::type().member().methodConst(person::str_updateAddress).build(&Person::updateAddress), + + rtl::type().member().methodStatic(person::str_getDefaults).build(&Person::getDefaults), + + rtl::type().member().methodStatic(person::str_createConst).build(&Person::createConst), + + rtl::type().member().methodStatic(person::str_getProfile).build(&Person::getProfile), + + rtl::type().member().methodStatic(person::str_getProfile).build(&Person::getProfile), + + rtl::type().member().methodStatic(person::str_getProfile).build(&Person::getProfile) + }); + + std::cout << "\n [t4]\trtl_tests::InitMirror::reflectingPerson() ==> Done.\n"; + } + + + void InitMirror::reflectingAnimal() + { + auto _ = rtl::CxxMirror({ + + rtl::type().record(animal::class_).build(), + + rtl::type().member().constructor().build(), + + rtl::type().member().method(animal::str_setFamilyName).build(&Animal::setFamilyName), + + rtl::type().member().methodConst(animal::str_getFamilyName).build(&Animal::getFamilyName), + + rtl::type().member().method(animal::str_setAnimalName).build(&Animal::setAnimalName), + + rtl::type().member().methodStatic(animal::str_updateZooKeeper).build(&Animal::updateZooKeeper), + + #if defined(__GNUC__) && !defined(__clang__) + /* GCC fails to automatically identify the correct overloaded functor to pick. (non-const-lvalue-ref & rvalue as argument) + we need to explicitly cast the functor like, static_cast(&Animal::setAnimalName). + */ rtl::type().member() + .method(animal::str_setAnimalName) + .build(static_cast(&Animal::setAnimalName)), //overloaded method, taking non-const lvalue reference as argument. + + rtl::type().member() + .method(animal::str_setAnimalName) + .build(static_cast(&Animal::setAnimalName)), //overloaded method, taking rvalue reference as argument. + + rtl::type().member() + .methodStatic(animal::str_updateZooKeeper) + .build(static_cast(&Animal::updateZooKeeper)), //static method, taking non-const lvalue reference as argument. + + rtl::type().member() + .methodStatic(animal::str_updateZooKeeper) + .build(static_cast(&Animal::updateZooKeeper)), //static method, taking rvalue reference as argument. + #else + rtl::type().member() + .method(animal::str_setAnimalName) + .build(&Animal::setAnimalName), + + rtl::type().member() + .method(animal::str_setAnimalName) + .build(&Animal::setAnimalName), + + rtl::type().member() + .methodStatic(animal::str_updateZooKeeper) + .build(&Animal::updateZooKeeper), + + rtl::type().member() + .methodStatic(animal::str_updateZooKeeper) + .build(&Animal::updateZooKeeper) + #endif + }); + + std::cout << "\n [t3]\trtl_tests::InitMirror::reflectingAnimal() ==> Done.\n"; + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/CxxMirrorTests/CxxMirrorThreadingTest.h b/RTLTestRunApp/src/CxxMirrorTests/CxxMirrorThreadingTest.h new file mode 100644 index 00000000..5facc836 --- /dev/null +++ b/RTLTestRunApp/src/CxxMirrorTests/CxxMirrorThreadingTest.h @@ -0,0 +1,20 @@ +#pragma once + +namespace rtl_tests +{ + struct InitMirror + { + static void reflectingDate(); + static void reflectingEvent(); + static void reflectingCalender(); + + static void reflectingBook(); + static void reflectingAnimal(); + static void reflectingPerson(); + static void reflectingLibrary(); + + static void reflectingPodsStl(); + static void reflectingMyTypePerson(); + static void reflectingCStyleFunctions(); + }; +} \ No newline at end of file diff --git a/RTLTestRunApp/src/FunctionalityTests/ClassMethodsTests.cpp b/RTLTestRunApp/src/FunctionalityTests/ClassMethodsTests.cpp new file mode 100644 index 00000000..ffd62a06 --- /dev/null +++ b/RTLTestRunApp/src/FunctionalityTests/ClassMethodsTests.cpp @@ -0,0 +1,574 @@ +#include + +#include "TestMirrorProvider.h" +#include "TestUtilsBook.h" +#include "TestUtilsDate.h" +#include "GlobalTestUtils.h" + +using namespace std; +using namespace rtl; + +using namespace test_utils; +using namespace test_mirror; + +namespace rtl_tests +{ + TEST(RTLInterfaceCxxMirror, get_class_methods_with_wrong_names) + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional badMethod = classBook->getMethod("no_method"); + ASSERT_FALSE(badMethod.has_value()); + } + + + TEST(RTLInterfaceCxxMirror, verify_typeIds_of_registered_records) + { + const auto& rtl_recordIdMap = cxx::mirror().getRecordIdMap(); + + for (const auto& itr0 : cxx::mirror().getNamespaceRecordMap()) + { + const auto& namespaceRecordMap = itr0.second; + for (const auto& itr1 : namespaceRecordMap) + { + const std::string& recordName = itr1.first; + const std::size_t recordId = reflected_id::getRecordIdFor(recordName); + const auto& itr = rtl_recordIdMap.find(recordId); + + ASSERT_TRUE(itr != rtl_recordIdMap.end()); + + const rtl::Record& reflectedClass = itr->second; + + auto [err, robj] = reflectedClass.create(); + + if (recordName == event::struct_) { + //Event's default constructor is private or deleted. + EXPECT_TRUE(err == rtl::error::TypeNotDefaultConstructible); + ASSERT_TRUE(robj.isEmpty()); + } + else if (recordName == library::class_) { + //Library's copy-constructor is deleted or private. + EXPECT_TRUE(err == rtl::error::TypeNotCopyConstructible); + ASSERT_TRUE(robj.isEmpty()); + } + else if (recordName == "void") { + //no constructor of class std::string is registered in RTL, but the calss is registered. + EXPECT_TRUE(err == rtl::error::TypeNotDefaultConstructible); + ASSERT_TRUE(robj.isEmpty()); + } + else { + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(robj.isEmpty()); + EXPECT_TRUE(robj.getTypeId() == recordId); + } + } + } + } + + + TEST(ReflectionMethodCall_heapInstance, wrong_args) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional setAuthor = classBook->getMethod(book::str_setAuthor); + ASSERT_TRUE(setAuthor); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + EXPECT_FALSE(setAuthor->hasSignature()); + + auto [err1, ret] = (*setAuthor)(book)(book::AUTHOR); + + EXPECT_TRUE(err1 == error::SignatureMismatch); + ASSERT_TRUE(ret.isEmpty()); + EXPECT_FALSE(book::test_method_setAuthor(book)); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ReflectionMethodCall_stackInstance, wrong_args) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional setAuthor = classBook->getMethod(book::str_setAuthor); + ASSERT_TRUE(setAuthor); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + EXPECT_FALSE(setAuthor->hasSignature()); + + auto [err1, ret] = (*setAuthor)(book)(book::AUTHOR); + + EXPECT_TRUE(err1 == error::SignatureMismatch); + ASSERT_TRUE(ret.isEmpty()); + EXPECT_FALSE(book::test_method_setAuthor(book)); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethod_heapInstance, args_void) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional getPublishedOn = classBook->getMethod(book::str_getPublishedOn); + ASSERT_TRUE(getPublishedOn); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + EXPECT_TRUE(getPublishedOn->hasSignature<>()); //empty template params checks for zero arguments. + // Slower. bind<>().call() syntax is faster. + auto [err1, ret] = (*getPublishedOn)(book)(); + + EXPECT_TRUE(err1 == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + const std::string& retStr = ret.view()->get(); + EXPECT_TRUE(book::test_method_getPublishedOn_return(retStr)); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethod_stackInstance, args_void) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional getPublishedOn = classBook->getMethod(book::str_getPublishedOn); + ASSERT_TRUE(getPublishedOn); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + EXPECT_TRUE(getPublishedOn->hasSignature<>()); //empty template params checks for zero arguments. + + auto [err1, ret] = (*getPublishedOn)(book)(); + + EXPECT_TRUE(err1 == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + const std::string& retStr = ret.view()->get(); + EXPECT_TRUE(book::test_method_getPublishedOn_return(retStr)); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethod_heapInstance, args_string) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional setAuthor = classBook->getMethod(book::str_setAuthor); + ASSERT_TRUE(setAuthor); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + EXPECT_TRUE(setAuthor->hasSignature()); + + auto author = std::string(book::AUTHOR); + auto [err1, ret] = setAuthor->bind(book).call(author); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret.isEmpty()); + EXPECT_TRUE(book::test_method_setAuthor(book)); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethod_stackInstance, args_string) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional setAuthor = classBook->getMethod(book::str_setAuthor); + ASSERT_TRUE(setAuthor); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + EXPECT_TRUE(setAuthor->hasSignature()); + + auto author = std::string(book::AUTHOR); + auto [err1, ret] = setAuthor->bind(book).call(author); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret.isEmpty()); + EXPECT_TRUE(book::test_method_setAuthor(book)); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethodOverload_heapInstance, args_void) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional updateBookInfo = classBook->getMethod(book::str_updateBookInfo); + ASSERT_TRUE(updateBookInfo); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + EXPECT_TRUE(updateBookInfo->hasSignature<>()); //empty template params checks for zero arguments. + + auto [err1, ret] = (*updateBookInfo)(book)(); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret.isEmpty()); + EXPECT_TRUE(book::test_method_updateBookInfo(book)); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethodOverload_stackInstance, args_void) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional updateBookInfo = classBook->getMethod(book::str_updateBookInfo); + ASSERT_TRUE(updateBookInfo); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + EXPECT_TRUE(updateBookInfo->hasSignature<>()); //empty template params checks for zero arguments. + + auto [err1, ret] = (*updateBookInfo)(book)(); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret.isEmpty()); + EXPECT_TRUE(book::test_method_updateBookInfo(book)); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethodOverload_heapInstance, args_string_double_charPtr) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional updateBookInfo = classBook->getMethod(book::str_updateBookInfo); + ASSERT_TRUE(updateBookInfo); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + + const bool signatureValid = updateBookInfo->hasSignature(); + EXPECT_TRUE(signatureValid); + + double price = book::PRICE; + std::string author = book::AUTHOR; + const char* title = book::TITLE; + + auto [err1, ret] = (*updateBookInfo)(book)(author, price, title); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret.isEmpty()); + + const bool isSuccess = book::test_method_updateBookInfo(book); + EXPECT_TRUE(isSuccess); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethodOverload_stackInstance, args_string_double_charPtr) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional updateBookInfo = classBook->getMethod(book::str_updateBookInfo); + ASSERT_TRUE(updateBookInfo); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + + const bool signatureValid = updateBookInfo->hasSignature(); + EXPECT_TRUE(signatureValid); + + double price = book::PRICE; + std::string author = book::AUTHOR; + const char* title = book::TITLE; + + auto [err1, ret] = (*updateBookInfo)(book)(author, price, title); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret.isEmpty()); + + const bool isSuccess = book::test_method_updateBookInfo(book); + EXPECT_TRUE(isSuccess); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethodOverload_heapInstance, args_charPtr_double_string) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional updateBookInfo = classBook->getMethod(book::str_updateBookInfo); + ASSERT_TRUE(updateBookInfo); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + + const bool signatureValid = updateBookInfo->hasSignature(); + EXPECT_TRUE(signatureValid); + + double price = book::PRICE; + std::string author = book::AUTHOR; + const char* title = book::TITLE; + + auto [err1, ret] = (*updateBookInfo)(book)(title, price, author); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret.isEmpty()); + + const bool isSuccess = book::test_method_updateBookInfo(book); + EXPECT_TRUE(isSuccess); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethodOverload_stackInstance, args_charPtr_double_string) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional updateBookInfo = classBook->getMethod(book::str_updateBookInfo); + ASSERT_TRUE(updateBookInfo); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + + const bool signatureValid = updateBookInfo->hasSignature(); + EXPECT_TRUE(signatureValid); + + double price = book::PRICE; + std::string author = book::AUTHOR; + const char* title = book::TITLE; + + auto [err1, ret] = (*updateBookInfo)(book)(title, price, author); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret.isEmpty()); + + const bool isSuccess = book::test_method_updateBookInfo(book); + EXPECT_TRUE(isSuccess); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethodOverload_stackInstance, method_args_const_string___call_with_non_const_string) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional addCopyrightTag = classBook->getMethod(book::str_addCopyrightTag); + ASSERT_TRUE(addCopyrightTag); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + + const bool signatureValid = addCopyrightTag->hasSignature(); + EXPECT_TRUE(signatureValid); + + //actual signature is 'const string', but we are passing 'string' as argument. which resolves to right call. + //as long as any param_type in signature is not reference, const-qualifier do not matter. + auto [err1, ret] = (*addCopyrightTag)(book)(std::string(book::COPYRIGHT_TAG)); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret.isEmpty()); + + const bool isSuccess = book::test_method_addCopyrightTag(book); + EXPECT_TRUE(isSuccess); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethodOverload_heapInstance, method_args_const_string___call_with_non_const_string) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional addCopyrightTag = classBook->getMethod(book::str_addCopyrightTag); + ASSERT_TRUE(addCopyrightTag); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + + const bool signatureValid = addCopyrightTag->hasSignature(); + EXPECT_TRUE(signatureValid); + + //actual signature is 'const string', but we are passing 'string' as argument. which resolves to right call. + //as long as any param_type in signature is not reference, const-qualifier do not matter. + auto [err1, ret] = (*addCopyrightTag)(book)(std::string(book::COPYRIGHT_TAG)); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret.isEmpty()); + + const bool isSuccess = book::test_method_addCopyrightTag(book); + EXPECT_TRUE(isSuccess); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethodOverload_stackInstance, method_taking_args_const_string_and_const_string_ref) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional addPreface = classBook->getMethod(book::str_addPreface); + ASSERT_TRUE(addPreface); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + + bool invalidSignature = addPreface->hasSignature(); + EXPECT_FALSE(invalidSignature); + + invalidSignature = addPreface->hasSignature(); + EXPECT_FALSE(invalidSignature); + + invalidSignature = addPreface->hasSignature(); + EXPECT_FALSE(invalidSignature); + + //if reference is involved, then const-qualifier must be exactly same as in signature reference type. + const bool signatureValid = addPreface->hasSignature(); + EXPECT_TRUE(signatureValid); + + const auto& preface = std::string(book::PREFACE); + const auto& acknowledgements = std::string(book::ACKNOWLEDGEMENTS); + + //if the signature has any one type as reference, then types must be explicitly specified using bind<...>() + //And reference type must be specified with exact qualifiers, other 'by value' types do no need to explicitly specify the cv-qualifiers. + auto [err1, ret] = addPreface->bind(book).call(acknowledgements, preface); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret.isEmpty()); + + const bool isSuccess = book::test_method_addPreface(book); + EXPECT_TRUE(isSuccess); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ClassBookMethodOverload_heapInstance, method_taking_args_const_string_and_const_string_ref) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional addPreface = classBook->getMethod(book::str_addPreface); + ASSERT_TRUE(addPreface); + + auto [err0, book] = classBook->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + + bool invalidSignature = addPreface->hasSignature(); + EXPECT_FALSE(invalidSignature); + + invalidSignature = addPreface->hasSignature(); + EXPECT_FALSE(invalidSignature); + + invalidSignature = addPreface->hasSignature(); + EXPECT_FALSE(invalidSignature); + + //if reference is involved, then const-qualifier must be exactly same as in signature reference type. + const bool signatureValid = addPreface->hasSignature(); + EXPECT_TRUE(signatureValid); + + const auto& preface = std::string(book::PREFACE); + const auto& acknowledgements = std::string(book::ACKNOWLEDGEMENTS); + + //if the signature has any one type as reference, then types must be explicitly specified using bind<...>() + //And reference type must be specified with exact qualifiers, other 'by value' types do no need to explicitly specify the cv-qualifiers. + auto [err1, ret] = addPreface->bind(book).call(acknowledgements, preface); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret.isEmpty()); + + const bool isSuccess = book::test_method_addPreface(book); + EXPECT_TRUE(isSuccess); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/FunctionalityTests/ConstMethodOverloadTests.cpp b/RTLTestRunApp/src/FunctionalityTests/ConstMethodOverloadTests.cpp new file mode 100644 index 00000000..87ba39ce --- /dev/null +++ b/RTLTestRunApp/src/FunctionalityTests/ConstMethodOverloadTests.cpp @@ -0,0 +1,546 @@ +#include + +#include "TestMirrorProvider.h" +#include "TestUtilsPerson.h" +#include "TestUtilsBook.h" + +using namespace std; +using namespace rtl; + +using namespace test_utils; +using namespace test_mirror; + +namespace rtl_tests +{ + TEST(ConstMethodOverload, explicitly_making_const_call__on_static_method) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional getDefaults = classPerson->getMethod(person::str_getDefaults); + ASSERT_TRUE(getDefaults); + EXPECT_TRUE(getDefaults->hasSignature<>()); + { + // enabling this results compiler error. + // auto [err, ret] = getDefaults->bind().call(); + // auto [err, ret] = getDefaults->bind().call(); + } + } + } + + + TEST(ConstMethodOverload, explicitly_making_const_call__on_wrong_target) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err0, book] = classBook->create(); + EXPECT_TRUE(err0 == error::None); + EXPECT_TRUE(book.isConstCastSafe()); + ASSERT_FALSE(book.isEmpty()); + + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional updateLastName = classPerson->getMethod(person::str_updateLastName); + ASSERT_TRUE(updateLastName); + EXPECT_TRUE(updateLastName->hasSignature()); + + string lastName = person::LAST_NAME; + { + auto [err, ret] = updateLastName->bind(constCast(book)).call(lastName); + + EXPECT_TRUE(err == error::TargetMismatch); + ASSERT_TRUE(ret.isEmpty()); + } { + auto [err, ret] = updateLastName->bind(constCast(book)).call(lastName); + + EXPECT_TRUE(err == error::TargetMismatch); + ASSERT_TRUE(ret.isEmpty()); + } + } + } + + + TEST(ConstMethodOverload, explicitly_making_const_call__on_empty_target) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional updateLastName = classPerson->getMethod(person::str_updateLastName); + ASSERT_TRUE(updateLastName); + EXPECT_TRUE(updateLastName->hasSignature()); + + string lastName = person::LAST_NAME; + { + auto [err, ret] = updateLastName->bind(constCast(RObject{ })).call(lastName); + + EXPECT_TRUE(err == error::EmptyRObject); + ASSERT_TRUE(ret.isEmpty()); + } { + auto [err, ret] = updateLastName->bind(constCast(RObject{ })).call(lastName); + + EXPECT_TRUE(err == error::EmptyRObject); + ASSERT_TRUE(ret.isEmpty()); + } + } + } + + + TEST(ConstMethodOverload, semantics_with_heap_target__only_const_method_exists) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional updateLastName = classPerson->getMethod(person::str_updateLastName); + ASSERT_TRUE(updateLastName); + + string lastName = person::LAST_NAME; + string firstName = person::FIRST_NAME; + auto [err0, person] = classPerson->create(firstName); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + // 'person' is created via reflection, so its logically-const, + // hence const_cast on the object bieng reflected is safe. + EXPECT_TRUE(person.isConstCastSafe()); + EXPECT_TRUE(updateLastName->hasSignature()); + + // this will by default bind to the const-method. + // Since the reflected object is bieng treated as 'const', so the + // 'const' method will be preffered with no-need of explicit resolution, since it exists. + auto [err, ret] = updateLastName->bind(person).call(lastName); + EXPECT_TRUE(err == error::None); + ASSERT_TRUE(ret.isEmpty()); + + EXPECT_TRUE(person::test_method_updateLastName_const(person)); + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ConstMethodOverload, semantics_with_stack_target__only_const_method_exists) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional updateLastName = classPerson->getMethod(person::str_updateLastName); + ASSERT_TRUE(updateLastName); + + string lastName = person::LAST_NAME; + string firstName = person::FIRST_NAME; + auto [err0, person] = classPerson->create(firstName); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + // 'person' is created via reflection, so its logically-const, + // hence const_cast on the object bieng reflected is safe. + EXPECT_TRUE(person.isConstCastSafe()); + EXPECT_TRUE(updateLastName->hasSignature()); + + // this will by default bind to the const-method. + // Since the reflected object is bieng treated as 'const', so the + // 'const' method will be preffered with no-need of explicit resolution, since it exists. + auto [err, ret] = updateLastName->bind(person).call(lastName); + EXPECT_TRUE(err == error::None); + ASSERT_TRUE(ret.isEmpty()); + + EXPECT_TRUE(person::test_method_updateLastName_const(person)); + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ConstMethodOverload, implicit_method_resolution__only_const_method_exists__on_heap_target) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional updateLastName = classPerson->getMethod(person::str_updateLastName); + ASSERT_TRUE(updateLastName); + + string firstName = person::FIRST_NAME; + auto [err0, person] = classPerson->create(firstName); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + // Objects created through reflection are considered mutable (non-const) by default. + EXPECT_TRUE(person.isConstCastSafe()); + EXPECT_TRUE(updateLastName->hasSignature()); + { + string_view lastName = "invalid_arg"; + auto [err, ret] = (*updateLastName)(person)(lastName); + + EXPECT_TRUE(err == error::SignatureMismatch); + ASSERT_TRUE(ret.isEmpty()); + } { + string lastName = person::LAST_NAME; + auto [err, ret] = (*updateLastName)(person)(lastName); + + EXPECT_TRUE(err == error::None); + ASSERT_TRUE(ret.isEmpty()); + } + EXPECT_TRUE(person::test_method_updateLastName_const(person)); + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ConstMethodOverload, implicit_method_resolution__only_const_method_exists__on_stack_target) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional updateLastName = classPerson->getMethod(person::str_updateLastName); + ASSERT_TRUE(updateLastName); + + string firstName = person::FIRST_NAME; + auto [err0, person] = classPerson->create(firstName); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + // Objects created through reflection are considered mutable (non-const) by default. + EXPECT_TRUE(person.isConstCastSafe()); + EXPECT_TRUE(updateLastName->hasSignature()); + { + string_view lastName = "invalid_arg"; + auto [err, ret] = (*updateLastName)(person)(lastName); + + EXPECT_TRUE(err == error::SignatureMismatch); + ASSERT_TRUE(ret.isEmpty()); + } { + string lastName = person::LAST_NAME; + auto [err, ret] = (*updateLastName)(person)(lastName); + + EXPECT_TRUE(err == error::None); + ASSERT_TRUE(ret.isEmpty()); + } + EXPECT_TRUE(person::test_method_updateLastName_const(person)); + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ConstMethodOverload, semantics_with_target_on_heap__overloads_exists) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional updateAddress = classPerson->getMethod(person::str_updateAddress); + ASSERT_TRUE(updateAddress); + + string firstName = person::FIRST_NAME; + auto [err0, person] = classPerson->create(firstName); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + // RTL treats objects created via reflection as logically immutable (i.e., 'const' by default). + // For such objects, applying a logical 'const_cast' is always safe, hence the check below is true. + // However, RTL respects the const-ness of objects originating outside RTL (e.g., return values). + // If an object is provided to RTL as 'const', a 'const_cast' would not be safe, and the check + // would return false. RTL never performs such unsafe casts internally. + EXPECT_TRUE(person.isConstCastSafe()); + EXPECT_TRUE(updateAddress->hasSignature()); + + auto address = string(person::ADDRESS); + { + // by default it calls the const-method overload. since + // it exists and the target is logically-const reflected-object. + auto [err, ret] = updateAddress->bind(person).call(address); + EXPECT_TRUE(err == error::None); + ASSERT_TRUE(ret.isEmpty()); + } { + // Since both the oveload exists then implicit call will bind to const-method by default, + // To explicitly choose the non-const method, we can explicitly bind by wrapping the target + // in 'rtl::constCast'. + auto [err, ret] = updateAddress->bind(constCast(person)).call(address); + EXPECT_TRUE(err == error::None); + ASSERT_TRUE(ret.isEmpty()); + } + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ConstMethodOverload, semantics_with_target_on_stack__overloads_exists) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional updateAddress = classPerson->getMethod(person::str_updateAddress); + ASSERT_TRUE(updateAddress); + + string firstName = person::FIRST_NAME; + auto [err0, person] = classPerson->create(firstName); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + // RTL treats objects created via reflection as logically immutable (i.e., 'const' by default). + // For such objects, applying a logical 'const_cast' is always safe, hence the check below is true. + // However, RTL respects the const-ness of objects originating outside RTL (e.g., return values). + // If an object is provided to RTL as 'const', a 'const_cast' would not be safe, and the check + // would return false. RTL never performs such unsafe casts internally. + EXPECT_TRUE(person.isConstCastSafe()); + EXPECT_TRUE(updateAddress->hasSignature()); + + auto address = string(person::ADDRESS); + { + // by default it calls the const-method overload. since + // it exists and the target is logically-const reflected-object. + auto [err, ret] = updateAddress->bind(person).call(address); + EXPECT_TRUE(err == error::None); + ASSERT_TRUE(ret.isEmpty()); + } { + // Since both the oveload exists then implicit call will bind to const-method by default, + // To explicitly choose the non-const method, we can explicitly bind by wrapping the target + // in 'rtl::constCast'. + auto [err, ret] = updateAddress->bind(constCast(person)).call(address); + EXPECT_TRUE(err == error::None); + ASSERT_TRUE(ret.isEmpty()); + } + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ConstMethodOverload, explicitly_bind_non_const_method_with_target_on_heap__only_const_method_exists) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional updateLastName = classPerson->getMethod(person::str_updateLastName); + ASSERT_TRUE(updateLastName); + + string lastName = person::LAST_NAME; + string firstName = person::FIRST_NAME; + + auto [err0, person] = classPerson->create(firstName); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + // Objects created through reflection are considered logically-immutable by default. So const_cast on them is safe + EXPECT_TRUE(person.isConstCastSafe()); + EXPECT_TRUE(updateLastName->hasSignature()); + { + auto [err, ret] = updateLastName->bind(constCast(person)).call(lastName); + + EXPECT_TRUE(err == error::NonConstOverloadMissing); + ASSERT_TRUE(ret.isEmpty()); + } { + auto [err, ret] = updateLastName->bind(constCast(person)).call(0); //invalid argument + + EXPECT_TRUE(err == error::SignatureMismatch); + ASSERT_TRUE(ret.isEmpty()); + } + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ConstMethodOverload, explicitly_bind_non_const_method_with_target_on_stack__only_const_method_exists) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional updateLastName = classPerson->getMethod(person::str_updateLastName); + ASSERT_TRUE(updateLastName); + + string lastName = person::LAST_NAME; + string firstName = person::FIRST_NAME; + auto [err0, person] = classPerson->create(firstName); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + // Objects created through reflection are considered mutable (non-const) by default. + EXPECT_TRUE(person.isConstCastSafe()); + EXPECT_TRUE(updateLastName->hasSignature()); + { + auto [err, ret] = updateLastName->bind(constCast(person)).call(lastName); + + EXPECT_TRUE(err == error::NonConstOverloadMissing); + ASSERT_TRUE(ret.isEmpty()); + } { + auto [err, ret] = updateLastName->bind(constCast(person)).call(0); //invalid argument + + EXPECT_TRUE(err == error::SignatureMismatch); + ASSERT_TRUE(ret.isEmpty()); + } + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ConstMethodOverload, explicit_non_const_method_resolution__only_non_const_method_exists__on_heap_target) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional getFirstName = classPerson->getMethod(person::str_getFirstName); + ASSERT_TRUE(getFirstName); + + string firstName = person::FIRST_NAME; + auto [err0, person] = classPerson->create(firstName); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + // Objects created through reflection are considered mutable (non-const) by default. + EXPECT_TRUE(person.isConstCastSafe()); + EXPECT_TRUE(getFirstName->hasSignature<>()); + { + auto [err, ret] = getFirstName->bind(constCast(person)).call(0); //invalid argument + + EXPECT_TRUE(err == error::SignatureMismatch); + ASSERT_TRUE(ret.isEmpty()); + } { + auto [err, ret] = getFirstName->bind(constCast(person)).call(); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + auto& fname = ret.view()->get(); + EXPECT_EQ(fname, firstName); + } + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ConstMethodOverload, explicit_non_const_method_resolution__only_non_const_method_exists__on_stack_target) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional getFirstName = classPerson->getMethod(person::str_getFirstName); + ASSERT_TRUE(getFirstName); + + string firstName = person::FIRST_NAME; + auto [err0, person] = classPerson->create(firstName); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + // Objects created through reflection are considered mutable (non-const) by default. + EXPECT_TRUE(person.isConstCastSafe()); + EXPECT_TRUE(getFirstName->hasSignature<>()); + { + auto [err, ret] = getFirstName->bind(constCast(person)).call(0); //invalid argument + + EXPECT_TRUE(err == error::SignatureMismatch); + ASSERT_TRUE(ret.isEmpty()); + } { + auto [err, ret] = getFirstName->bind(constCast(person)).call(); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + auto& fname = ret.view()->get(); + EXPECT_EQ(fname, firstName); + } + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ConstMethodOverload, explicit_method_resolution__only_non_const_method_exists__call_on_returned_const_target) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional createConstPerson = classPerson->getMethod(person::str_createConst); + ASSERT_TRUE(createConstPerson); + + auto [err0, constPerson] = createConstPerson->bind().call(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(constPerson.isEmpty()); + // RTL treats own objects as mutable (logical const enforced), preserves external const; type system ensures const-safety. + EXPECT_FALSE(constPerson.isConstCastSafe()); + + optional getFirstName = classPerson->getMethod(person::str_getFirstName); + ASSERT_TRUE(getFirstName); + + string firstName = person::FIRST_NAME; + EXPECT_TRUE(getFirstName->hasSignature<>()); + { + auto [err, ret] = getFirstName->bind(constPerson).call(); + + // A non-const version exists, but the object itself is truly const. + // Therefore, only const-qualified methods can be called on it. + // However, no const-overload is available. + EXPECT_TRUE(err == error::ConstOverloadMissing); + ASSERT_TRUE(ret.isEmpty()); + } { + auto [err, ret] = getFirstName->bind(constCast(constPerson)).call(); + EXPECT_TRUE(err == error::IllegalConstCast); + ASSERT_TRUE(ret.isEmpty()); + } + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ConstMethodOverload, explicit_method_resolution__only_non_const_method_exists__call_on_returned_const_pointer_target) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional createConstPtrPerson = classPerson->getMethod(person::str_createPtr); + ASSERT_TRUE(createConstPtrPerson); + + // Returns 'const Person*', unmanaged, need explicit call to 'delete'. + auto [err0, constPersonPtr] = createConstPtrPerson->bind().call(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(constPersonPtr.isEmpty()); + // RTL treats own objects as mutable (logical const enforced), preserves external const; type system ensures const-safety. + EXPECT_FALSE(constPersonPtr.isConstCastSafe()); + + optional getFirstName = classPerson->getMethod(person::str_getFirstName); + ASSERT_TRUE(getFirstName); + + string firstName = person::FIRST_NAME; + EXPECT_TRUE(getFirstName->hasSignature<>()); + { + auto [err, ret] = getFirstName->bind(constPersonPtr).call(); + // A non-const version exists, but the object itself is truly const. + // Therefore, only const-qualified methods can be called on it. + // However, no const-overload is available. + EXPECT_TRUE(err == error::ConstOverloadMissing); + ASSERT_TRUE(ret.isEmpty()); + } { + auto [err, ret] = getFirstName->bind(constCast(constPersonPtr)).call(); + + EXPECT_TRUE(err == error::IllegalConstCast); + ASSERT_TRUE(ret.isEmpty()); + } + EXPECT_TRUE(person::delete_unmanaged_person_instance_created_via_createPtr(constPersonPtr)); + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/FunctionalityTests/ConstructorTests.cpp b/RTLTestRunApp/src/FunctionalityTests/ConstructorTests.cpp new file mode 100644 index 00000000..66ad00e4 --- /dev/null +++ b/RTLTestRunApp/src/FunctionalityTests/ConstructorTests.cpp @@ -0,0 +1,347 @@ +#include + +#include "TestMirrorProvider.h" +#include "TestUtilsBook.h" +#include "TestUtilsDate.h" + +using namespace std; +using namespace rtl; + +using namespace test_utils; +using namespace test_mirror; + +namespace rtl_tests +{ + TEST(RTLInterfaceCxxMirror, get_record_types_with_wrong_names) + { + optional badFunc = cxx::mirror().getFunction(date::ns, "wrong_date_struct"); + EXPECT_FALSE(badFunc); + + optional badRec = cxx::mirror().getRecord(date::ns, "wrong" + std::string(date::struct_)); + EXPECT_FALSE(badRec); + } + + + TEST(HeapAllocConstructorDate, wrong_args) + { + { + optional classDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(classDate); + + auto [err, date] = classDate->create("wrong", "args0", 10); + + EXPECT_TRUE(err == error::SignatureMismatch); + ASSERT_TRUE(date.isEmpty()); + } + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(StackAllocConstructorDate, wrong_args) + { + { + optional classDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(classDate); + + auto [err, date] = classDate->create("wrong", "args0", 10); + + EXPECT_TRUE(err == error::SignatureMismatch); + ASSERT_TRUE(date.isEmpty()); + } + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(HeapAllocConstructorDate, args_void) + { + { + optional classDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(classDate); + + auto [err, date] = classDate->create(); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(date.isEmpty()); + EXPECT_TRUE(date::test_dynamic_alloc_instance_ctor<>(date)); + } + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(StackAllocConstructorDate, args_void) + { + { + optional classDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(classDate); + + auto [err, date] = classDate->create(); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(date.isEmpty()); + EXPECT_TRUE(date::test_dynamic_alloc_instance_ctor<>(date)); + } + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(HeapAllocConstructorDate, args_string) + { + { + optional classDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(classDate); + + string dateStr = date::DATE_STR0; + auto [err, date] = classDate->create(dateStr); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(date.isEmpty()); + EXPECT_TRUE(date::test_dynamic_alloc_instance_ctor(date)); + } + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(StackAllocConstructorDate, args_string) + { + { + optional classDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(classDate); + + string dateStr = date::DATE_STR0; + auto [err, date] = classDate->create(dateStr); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(date.isEmpty()); + EXPECT_TRUE(date::test_dynamic_alloc_instance_ctor(date)); + } + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(HeapAllocConstructorDate, args_unsigned_unsigned_unsigned) + { + { + optional classDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(classDate); + + unsigned day = date::DAY; + unsigned month = date::MONTH; + unsigned year = date::YEAR; + + auto [err, date] = classDate->create(day, month, year); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(date.isEmpty()); + + const bool isPassed = date::test_dynamic_alloc_instance_ctor(date); + EXPECT_TRUE(isPassed); + } + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(StackAllocConstructorDate, args_unsigned_unsigned_unsigned) + { + { + optional classDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(classDate); + + unsigned day = date::DAY; + unsigned month = date::MONTH; + unsigned year = date::YEAR; + + auto [err, date] = classDate->create(day, month, year); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(date.isEmpty()); + + const bool isPassed = date::test_dynamic_alloc_instance_ctor(date); + EXPECT_TRUE(isPassed); + } + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(DestructorDate, non_virtual_on_heap) + { + { + optional classDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(classDate); + + auto [err, date] = classDate->create(); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(date.isEmpty()); + EXPECT_TRUE(date::test_dynamic_alloc_instance_ctor<>(date)); + } + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(DestructorDate, non_virtual_on_stack) + { + { + optional classDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(classDate); + + auto [err, date] = classDate->create(); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(date.isEmpty()); + EXPECT_TRUE(date::test_dynamic_alloc_instance_ctor<>(date)); + } + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(HeapAllocConstructorBook, wrong_args) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err, book] = classBook->create(19.0, 87.5); + + EXPECT_TRUE(err == error::SignatureMismatch); + ASSERT_TRUE(book.isEmpty()); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(StackAllocConstructorBook, wrong_args) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err, book] = classBook->create(19.0, 87.5); + + EXPECT_TRUE(err == error::SignatureMismatch); + ASSERT_TRUE(book.isEmpty()); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(HeapAllocConstructorBook, args_default) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err, book] = classBook->create(); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(book.isEmpty()); + EXPECT_TRUE(book::test_dynamic_alloc_instance_ctor(book)); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(StackAllocConstructorBook, args_default) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err, book] = classBook->create(); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(book.isEmpty()); + EXPECT_TRUE(book::test_dynamic_alloc_instance_ctor(book)); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(HeapAllocConstructorBook, args_double_string) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + double price = book::PRICE; + string title = book::TITLE; + auto [err, book] = classBook->create(price, title); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(book.isEmpty()); + + const bool isPassed = book::test_dynamic_alloc_instance_ctor(book); + EXPECT_TRUE(isPassed); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(StackAllocConstructorBook, args_double_string) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + double price = book::PRICE; + string title = book::TITLE; + auto [err, book] = classBook->create(price, title); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(book.isEmpty()); + + const bool isPassed = book::test_dynamic_alloc_instance_ctor(book); + EXPECT_TRUE(isPassed); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(DestructorBook, non_virtual_on_heap) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err, book] = classBook->create(); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(book.isEmpty()); + EXPECT_TRUE(book::test_dynamic_alloc_instance_ctor(book)); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(DestructorBook, non_virtual_on_stack) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err, book] = classBook->create(); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(book.isEmpty()); + EXPECT_TRUE(book::test_dynamic_alloc_instance_ctor(book)); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/FunctionalityTests/CopyConstructorTests.cpp b/RTLTestRunApp/src/FunctionalityTests/CopyConstructorTests.cpp new file mode 100644 index 00000000..a0de7709 --- /dev/null +++ b/RTLTestRunApp/src/FunctionalityTests/CopyConstructorTests.cpp @@ -0,0 +1,574 @@ +#include + +#include "TestMirrorProvider.h" +#include "TestUtilsBook.h" +#include "TestUtilsDate.h" + +using namespace std; +using namespace rtl; + +using namespace test_utils; +using namespace test_mirror; + +namespace rtl_tests +{ + TEST(CopyConstructor, clone_default_instance_on_heap_source_on_heap) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err0, book0] = classBook->create(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book0.isEmpty()); + + auto [err1, book1] = book0.clone(); + + EXPECT_TRUE(err1 == error::None); + ASSERT_FALSE(book1.isEmpty()); + + EXPECT_TRUE(book::get_book_instance_count() == 2); + EXPECT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 2); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(CopyConstructor, clone_default_instance_on_stack_source_on_stack) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err0, book0] = classBook->create(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book0.isEmpty()); + + auto [err1, book1] = book0.clone(); + + EXPECT_TRUE(err1 == error::None); + ASSERT_FALSE(book1.isEmpty()); + + EXPECT_TRUE(book::get_book_instance_count() == 2); + EXPECT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(CopyConstructor, clone_default_instance_on_heap_source_on_stack) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err0, book0] = classBook->create(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book0.isEmpty()); + + auto [err1, book1] = book0.clone(); + + EXPECT_TRUE(err1 == error::None); + ASSERT_FALSE(book1.isEmpty()); + + EXPECT_TRUE(book::get_book_instance_count() == 2); + EXPECT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + + TEST(CopyConstructor, clone_default_instance_on_stack_source_on_heap) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err0, book0] = classBook->create(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book0.isEmpty()); + + auto [err1, book1] = book0.clone(); + + EXPECT_TRUE(err1 == error::None); + ASSERT_FALSE(book1.isEmpty()); + + EXPECT_TRUE(book::get_book_instance_count() == 2); + EXPECT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(CopyConstructor, clone_mutated_instance_on_heap_source_on_heap) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional setAuthor = classBook->getMethod(book::str_setAuthor); + ASSERT_TRUE(setAuthor); + + optional setDecription = classBook->getMethod(book::str_setDescription); + ASSERT_TRUE(setDecription); + + double price = book::PRICE; + string title = book::TITLE; + string author = book::AUTHOR; + string description = book::DESCRIPTION; + + auto [err0, book] = classBook->create(price, title); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + + auto [err1, ret1] = (*setAuthor)(book)(author); + EXPECT_TRUE(err1 == error::None); + + auto [err2, ret2] = (*setDecription)(book)(description); + EXPECT_TRUE(err1 == error::None); + + auto [err3, bookCopy] = book.clone(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(bookCopy.isEmpty()); + + const bool isPassed = book::test_copy_ctor_with_mutated_object(bookCopy); + EXPECT_TRUE(isPassed); + + EXPECT_TRUE(book::get_book_instance_count() == 2); + EXPECT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 2); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(CopyConstructor, clone_mutated_instance_on_stack_source_on_stack) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional setAuthor = classBook->getMethod(book::str_setAuthor); + ASSERT_TRUE(setAuthor); + + optional setDecription = classBook->getMethod(book::str_setDescription); + ASSERT_TRUE(setDecription); + + double price = book::PRICE; + string title = book::TITLE; + string author = book::AUTHOR; + string description = book::DESCRIPTION; + + auto [err0, book] = classBook->create(price, title); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + + auto [err1, ret1] = (*setAuthor)(book)(author); + EXPECT_TRUE(err1 == error::None); + + auto [err2, ret2] = (*setDecription)(book)(description); + EXPECT_TRUE(err1 == error::None); + + auto [err3, bookCopy] = book.clone(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(bookCopy.isEmpty()); + + const bool isPassed = book::test_copy_ctor_with_mutated_object(bookCopy); + EXPECT_TRUE(isPassed); + + EXPECT_TRUE(book::get_book_instance_count() == 2); + EXPECT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(CopyConstructor, clone_mutated_instance_on_heap_source_on_stack) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional setAuthor = classBook->getMethod(book::str_setAuthor); + ASSERT_TRUE(setAuthor); + + optional setDecription = classBook->getMethod(book::str_setDescription); + ASSERT_TRUE(setDecription); + + double price = book::PRICE; + string title = book::TITLE; + string author = book::AUTHOR; + string description = book::DESCRIPTION; + + auto [err0, book] = classBook->create(price, title); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + + auto [err1, ret1] = (*setAuthor)(book)(author); + EXPECT_TRUE(err1 == error::None); + + auto [err2, ret2] = (*setDecription)(book)(description); + EXPECT_TRUE(err1 == error::None); + + auto [err3, bookCopy] = book.clone(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(bookCopy.isEmpty()); + + const bool isPassed = book::test_copy_ctor_with_mutated_object(bookCopy); + EXPECT_TRUE(isPassed); + + EXPECT_TRUE(book::get_book_instance_count() == 2); + EXPECT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(CopyConstructor, clone_mutated_instance_on_stack_source_on_heap) + { + { + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + optional setAuthor = classBook->getMethod(book::str_setAuthor); + ASSERT_TRUE(setAuthor); + + optional setDecription = classBook->getMethod(book::str_setDescription); + ASSERT_TRUE(setDecription); + + double price = book::PRICE; + string title = book::TITLE; + string author = book::AUTHOR; + string description = book::DESCRIPTION; + + auto [err0, book] = classBook->create(price, title); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(book.isEmpty()); + + auto [err1, ret1] = (*setAuthor)(book)(author); + EXPECT_TRUE(err1 == error::None); + + auto [err2, ret2] = (*setDecription)(book)(description); + EXPECT_TRUE(err1 == error::None); + + auto [err3, bookCopy] = book.clone(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(bookCopy.isEmpty()); + + const bool isPassed = book::test_copy_ctor_with_mutated_object(bookCopy); + EXPECT_TRUE(isPassed); + + EXPECT_TRUE(book::get_book_instance_count() == 2); + EXPECT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + } + EXPECT_TRUE(book::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(CopyConstructor, sharing_semantics__clone_on_stack_src_on_stack_mutate_after) + { + { + // Retrieve the reflected Record for the 'Calender' struct + optional typeCalender = cxx::mirror().getRecord(calender::ns, calender::struct_); + ASSERT_TRUE(typeCalender); + + // Create a stack-allocated object via reflection + auto [err0, calender0] = typeCalender->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(calender0.isEmpty()); + EXPECT_FALSE(calender0.isOnHeap()); + + EXPECT_TRUE(calender::get_instance_count() == 1); + // 'Calender' has 2 'Event' instances, shared_ptr and a std::unique_ptr. + EXPECT_TRUE(event::get_instance_count() == 2); + // 'Event' has a unique_ptr and two 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 2); + + // The underlying object is expected to be copied via copy constructor. + auto [err1, calender1] = calender0.clone(); + + EXPECT_TRUE(err1 == error::None); + // Verify the object created is valid and on stack. + ASSERT_FALSE(calender1.isEmpty()); + EXPECT_FALSE(calender1.isOnHeap()); + EXPECT_TRUE(calender0.getTypeId() == calender1.getTypeId()); + + // Calender got cloned now. + EXPECT_TRUE(calender::get_instance_count() == 2); + // 'Calender' has shared_ptr and a std::unique_ptr, so one got shared and one newly created. + EXPECT_TRUE(event::get_instance_count() == 3); + // 'Event' has a unique_ptr and 3 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 3); + + optional getTheDate = typeCalender->getMethod(calender::str_getTheDate); + ASSERT_TRUE(getTheDate); + { + auto [err_0, date0] = getTheDate->bind(calender0).call(); + EXPECT_TRUE(err_0 == error::None); + EXPECT_FALSE(date0.isOnHeap()); + ASSERT_FALSE(date0.isEmpty()); + EXPECT_TRUE(date0.isConstCastSafe()); + + auto [err_1, date1] = getTheDate->bind(calender1).call(); + EXPECT_TRUE(err_1 == error::None); + EXPECT_FALSE(date1.isOnHeap()); + ASSERT_FALSE(date1.isEmpty()); + + // both objects must be equal (shared via shared_ptr inside 'Calender') + EXPECT_TRUE(date::test_if_obejcts_are_equal(date0, date1)); + + optional structDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(structDate); + optional updateDate = structDate->getMethod(date::str_updateDate); + ASSERT_TRUE(updateDate); + EXPECT_FALSE(updateDate->isConst()); + string dateStr = date::DATE_STR1; + { + auto [err, ret] = updateDate->bind(constCast(date0)).call(dateStr); + ASSERT_TRUE(err == error::None && ret.isEmpty()); + // After mutation, they should be still equal. + EXPECT_TRUE(date::test_if_obejcts_are_equal(date0, date1)); + } + } + } + // After scope exit, stack instances are cleaned up automatically + EXPECT_TRUE(calender::get_instance_count() == 0); + EXPECT_TRUE(event::get_instance_count() == 0); + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(CopyConstructor, sharing_semantics__clone_on_heap_src_on_stack_mutate_after) + { + { + // Retrieve the reflected Record for the 'Calender' struct + optional typeCalender = cxx::mirror().getRecord(calender::ns, calender::struct_); + ASSERT_TRUE(typeCalender); + + // Create a stack-allocated object via reflection + auto [err0, calender0] = typeCalender->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(calender0.isEmpty()); + EXPECT_FALSE(calender0.isOnHeap()); + + EXPECT_TRUE(calender::get_instance_count() == 1); + // 'Calender' has 2 'Event' instances, shared_ptr and a std::unique_ptr. + EXPECT_TRUE(event::get_instance_count() == 2); + // 'Event' has a unique_ptr and two 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 2); + + // The underlying object is expected to be copied via copy constructor. + auto [err1, calender1] = calender0.clone(); + + EXPECT_TRUE(err1 == error::None); + // Verify the object created is valid and on stack. + ASSERT_FALSE(calender1.isEmpty()); + EXPECT_TRUE(calender1.isOnHeap()); + EXPECT_TRUE(calender0.getTypeId() == calender1.getTypeId()); + + // Calender got cloned now. + EXPECT_TRUE(calender::get_instance_count() == 2); + // 'Calender' has shared_ptr and a std::unique_ptr, so one got shared and one newly created. + EXPECT_TRUE(event::get_instance_count() == 3); + // 'Event' has a unique_ptr and 3 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 3); + + optional getTheDate = typeCalender->getMethod(calender::str_getTheDate); + ASSERT_TRUE(getTheDate); + { + auto [err_0, date0] = getTheDate->bind(calender0).call(); + EXPECT_TRUE(err_0 == error::None); + EXPECT_FALSE(date0.isOnHeap()); + ASSERT_FALSE(date0.isEmpty()); + + auto [err_1, date1] = getTheDate->bind(calender1).call(); + EXPECT_TRUE(err_1 == error::None); + EXPECT_FALSE(date1.isOnHeap()); + ASSERT_FALSE(date1.isEmpty()); + + // both objects must be equal (shared via shared_ptr inside 'Calender') + EXPECT_TRUE(date::test_if_obejcts_are_equal(date0, date1)); + + optional structDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(structDate); + optional updateDate = structDate->getMethod(date::str_updateDate); + ASSERT_TRUE(updateDate); + // 'updateDate' is non-const member function in 'Date' class. + EXPECT_FALSE(updateDate->isConst()); + string dateStr = date::DATE_STR1; + { + auto [err, ret] = updateDate->bind(constCast(date0)).call(dateStr); + ASSERT_TRUE(err == error::None && ret.isEmpty()); + // After mutation, they should be still equal. + EXPECT_TRUE(date::test_if_obejcts_are_equal(date0, date1)); + } + } + } + // After scope exit, stack instances are cleaned up automatically + EXPECT_TRUE(calender::get_instance_count() == 0); + EXPECT_TRUE(event::get_instance_count() == 0); + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(CopyConstructor, sharing_semantics__clone_on_stack_src_on_heap_mutate_after) + { + { + // Retrieve the reflected Record for the 'Calender' struct + optional typeCalender = cxx::mirror().getRecord(calender::ns, calender::struct_); + ASSERT_TRUE(typeCalender); + + // Create a stack-allocated object via reflection + auto [err0, calender0] = typeCalender->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(calender0.isEmpty()); + EXPECT_TRUE(calender0.isOnHeap()); + + EXPECT_TRUE(calender::get_instance_count() == 1); + // 'Calender' has 2 'Event' instances, shared_ptr and a std::unique_ptr. + EXPECT_TRUE(event::get_instance_count() == 2); + // 'Event' has a unique_ptr and two 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 2); + + // The underlying object is expected to be copied via copy constructor. + auto [err1, calender1] = calender0.clone(); + + EXPECT_TRUE(err1 == error::None); + // Verify the object created is valid and on stack. + ASSERT_FALSE(calender1.isEmpty()); + EXPECT_FALSE(calender1.isOnHeap()); + EXPECT_TRUE(calender0.getTypeId() == calender1.getTypeId()); + + // Calender got cloned now. + EXPECT_TRUE(calender::get_instance_count() == 2); + // 'Calender' has shared_ptr and a std::unique_ptr, so one got shared and one newly created. + EXPECT_TRUE(event::get_instance_count() == 3); + // 'Event' has a unique_ptr and 3 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 3); + + optional getTheDate = typeCalender->getMethod(calender::str_getTheDate); + ASSERT_TRUE(getTheDate); + { + auto [err_0, date0] = getTheDate->bind(calender0).call(); + EXPECT_TRUE(err_0 == error::None); + EXPECT_FALSE(date0.isOnHeap()); + ASSERT_FALSE(date0.isEmpty()); + + auto [err_1, date1] = getTheDate->bind(calender1).call(); + EXPECT_TRUE(err_1 == error::None); + EXPECT_FALSE(date1.isOnHeap()); + ASSERT_FALSE(date1.isEmpty()); + + // both objects must be equal (shared via shared_ptr inside 'Calender') + EXPECT_TRUE(date::test_if_obejcts_are_equal(date0, date1)); + + optional structDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(structDate); + optional updateDate = structDate->getMethod(date::str_updateDate); + ASSERT_TRUE(updateDate); + // 'updateDate' is non-const member function in 'Date' class. + EXPECT_FALSE(updateDate->isConst()); + string dateStr = date::DATE_STR1; + { + auto [err, ret] = updateDate->bind(constCast(date0)).call(dateStr); + ASSERT_TRUE(err == error::None && ret.isEmpty()); + // After mutation, they should be still equal. + EXPECT_TRUE(date::test_if_obejcts_are_equal(date0, date1)); + } + } + } + // After scope exit, stack instances are cleaned up automatically + EXPECT_TRUE(calender::get_instance_count() == 0); + EXPECT_TRUE(event::get_instance_count() == 0); + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(CopyConstructor, sharing_semantics__clone_on_heap_src_on_heap_mutate_after) + { + { + // Retrieve the reflected Record for the 'Calender' struct + optional typeCalender = cxx::mirror().getRecord(calender::ns, calender::struct_); + ASSERT_TRUE(typeCalender); + + // Create a stack-allocated object via reflection + auto [err0, calender0] = typeCalender->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(calender0.isEmpty()); + EXPECT_TRUE(calender0.isOnHeap()); + + EXPECT_TRUE(calender::get_instance_count() == 1); + // 'Calender' has 2 'Event' instances, shared_ptr and a std::unique_ptr. + EXPECT_TRUE(event::get_instance_count() == 2); + // 'Event' has a unique_ptr and two 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 2); + + // The underlying object is expected to be copied via copy constructor. + auto [err1, calender1] = calender0.clone(); + + EXPECT_TRUE(err1 == error::None); + // Verify the object created is valid and on stack. + ASSERT_FALSE(calender1.isEmpty()); + EXPECT_TRUE(calender1.isOnHeap()); + EXPECT_TRUE(calender0.getTypeId() == calender1.getTypeId()); + + // Calender got cloned now. + EXPECT_TRUE(calender::get_instance_count() == 2); + // 'Calender' has shared_ptr and a std::unique_ptr, so one got shared and one newly created. + EXPECT_TRUE(event::get_instance_count() == 3); + // 'Event' has a unique_ptr and 3 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 3); + + optional getSavedDate = typeCalender->getMethod(calender::str_getSavedDate); + ASSERT_TRUE(getSavedDate); + { + auto [err_0, date0] = getSavedDate->bind(calender0).call(); + EXPECT_TRUE(err_0 == error::None); + EXPECT_FALSE(date0.isOnHeap()); + ASSERT_FALSE(date0.isEmpty()); + + auto [err_1, date1] = getSavedDate->bind(calender1).call(); + EXPECT_TRUE(err_1 == error::None); + EXPECT_FALSE(date1.isOnHeap()); + ASSERT_FALSE(date1.isEmpty()); + + // both objects must be equal, created via default-constructor, different instances, not shared. + EXPECT_TRUE(date::test_if_obejcts_are_equal(date0, date1)); + + optional structDate = cxx::mirror().getRecord(date::ns, date::struct_); + ASSERT_TRUE(structDate); + optional updateDate = structDate->getMethod(date::str_updateDate); + ASSERT_TRUE(updateDate); + // 'updateDate' is non-const member function in 'Date' class. + EXPECT_FALSE(updateDate->isConst()); + string dateStr = date::DATE_STR1; + { + auto [err, ret] = updateDate->bind(constCast(date0)).call(dateStr); + ASSERT_TRUE(err == error::None && ret.isEmpty()); + // After mutation, they should be not be equal, since both are unique instances. + EXPECT_FALSE(date::test_if_obejcts_are_equal(date0, date1)); + } + } + } + // After scope exit, stack instances are cleaned up automatically + EXPECT_TRUE(calender::get_instance_count() == 0); + EXPECT_TRUE(event::get_instance_count() == 0); + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } +} diff --git a/RTLTestRunApp/src/FunctionalityTests/MoveConstructorTests.cpp b/RTLTestRunApp/src/FunctionalityTests/MoveConstructorTests.cpp new file mode 100644 index 00000000..d4c07bc0 --- /dev/null +++ b/RTLTestRunApp/src/FunctionalityTests/MoveConstructorTests.cpp @@ -0,0 +1,275 @@ + +#include + +#include "TestMirrorProvider.h" +#include "TestUtilsDate.h" + +using namespace std; +using namespace rtl; + +using namespace test_utils; +using namespace test_mirror; + +namespace rtl_tests +{ + TEST(MoveSemantics, move_reflected_type_allocated_on_stack) + { + { + // Retrieve the reflected Record for the 'Calender' struct + optional classCalender = cxx::mirror().getRecord(calender::ns, calender::struct_); + ASSERT_TRUE(classCalender); + + // Create a stack-allocated object via reflection + auto [err0, calender0] = classCalender->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(calender0.isEmpty()); + EXPECT_TRUE(calender0.isConstCastSafe()); + EXPECT_FALSE(calender0.isOnHeap()); + + EXPECT_TRUE(calender::get_instance_count() == 1); + // 'Calender' has 2 'Event' instances, shared_ptr and a std::unique_ptr. + EXPECT_TRUE(event::get_instance_count() == 2); + // 'Event' has a unique_ptr and two 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 2); + + // Sets Calender's move operation counter to zero + calender::reset_move_ops_counter(); + + // Moving a RObject created via alloc::Stack, invokes Calender's move constructor. + RObject calender1 = std::move(calender0); + + //TODO: Fails on linux, differently optimized away from windows? + // Calender's move-constructor called once. + // EXPECT_TRUE(calender::get_move_ops_count() == 1); + + ASSERT_FALSE(calender1.isEmpty()); + EXPECT_TRUE(calender1.isConstCastSafe()); + EXPECT_FALSE(calender1.isOnHeap()); + + // 'calander0' must be empty now. + ASSERT_TRUE(calender0.isEmpty()); + EXPECT_NE(calender0.getTypeId(), calender1.getTypeId()); + + // After move, these instance count must remain same. + EXPECT_TRUE(calender::get_instance_count() == 1); + // 'Calender' has 2 'Event' instances, shared_ptr and a std::unique_ptr. + EXPECT_TRUE(event::get_instance_count() == 2); + // 'Event' has a unique_ptr and two 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 2); + { + // Cloning a moved-from object ie an empty object; + auto [err, ret] = calender0.clone(); + EXPECT_TRUE(err == error::EmptyRObject); + ASSERT_TRUE(ret.isEmpty()); + } { + // Cloning a moved-from object ie an empty object; + auto [err, ret] = calender0.clone(); + EXPECT_TRUE(err == error::EmptyRObject); + ASSERT_TRUE(ret.isEmpty()); + } + } + // After scope exit, stack instances are cleaned up automatically + EXPECT_TRUE(calender::get_instance_count() == 0); + EXPECT_TRUE(event::get_instance_count() == 0); + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(MoveSemantics, move_reflected_type_allocated_on_heap) + { + { + // Retrieve the reflected Record for the 'Calender' struct + optional classCalender = cxx::mirror().getRecord(calender::ns, calender::struct_); + ASSERT_TRUE(classCalender); + + // Create a stack-allocated object via reflection + auto [err0, calender0] = classCalender->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(calender0.isEmpty()); + EXPECT_TRUE(calender0.isConstCastSafe()); + EXPECT_TRUE(calender0.isOnHeap()); + + EXPECT_TRUE(calender::get_instance_count() == 1); + // 'Calender' has 2 'Event' instances, shared_ptr and a std::unique_ptr. + EXPECT_TRUE(event::get_instance_count() == 2); + // 'Event' has a unique_ptr and two 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 2); + + // Sets Calender's move operation counter to zero + calender::reset_move_ops_counter(); + + // RObject created via alloc::HEAP, contains pointer to reflected type internally, So just the + // address wrapped in std::any inside Robject is moved. Calender's move constructor is not called. + RObject calender1 = std::move(calender0); + + // Calender's move constructor isn't called. + EXPECT_TRUE(calender::get_move_ops_count() == 0); + + ASSERT_FALSE(calender1.isEmpty()); + EXPECT_TRUE(calender1.isConstCastSafe()); + EXPECT_TRUE(calender1.isOnHeap()); + + // 'calander0' must be empty now. + ASSERT_TRUE(calender0.isEmpty()); + EXPECT_NE(calender0.getTypeId(), calender1.getTypeId()); + + // After move, these instance count must remain same. + EXPECT_TRUE(calender::get_instance_count() == 1); + // 'Calender' has 2 'Event' instances, shared_ptr and a std::unique_ptr. + EXPECT_TRUE(event::get_instance_count() == 2); + // 'Event' has a unique_ptr and two 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 2); + } + // After scope exit, stack instances are cleaned up automatically + EXPECT_TRUE(calender::get_instance_count() == 0); + EXPECT_TRUE(event::get_instance_count() == 0); + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(MoveSemantics, move_returned_RObject_reflecting_true_const_ref) + { + { + // Retrieve the reflected Record for the 'Calender' struct + optional classCalender = cxx::mirror().getRecord(calender::ns, calender::struct_); + ASSERT_TRUE(classCalender); + + optional getTheEvent = classCalender->getMethod(calender::str_getTheEvent); + ASSERT_TRUE(getTheEvent); + + // Create a stack-allocated object via reflection + auto [err, calender] = classCalender->create(); + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(calender.isEmpty()); + + EXPECT_TRUE(calender::get_instance_count() == 1); + // 'Calender' has 2 'Event' instances, shared_ptr and a std::unique_ptr. + EXPECT_TRUE(event::get_instance_count() == 2); + // 'Event' has a unique_ptr and two 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 2); + { + // getTheEvent() returns 'const Event&', hence Reflecetd as true-const. + auto [err0, event0] = getTheEvent->bind(calender).call(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(event0.isEmpty()); + EXPECT_FALSE(event0.isConstCastSafe()); // Retured as True-Const from reflected call, even RTL will not const_cast it. + + optional classEvent = cxx::mirror().getRecord(event::ns, event::struct_); + ASSERT_TRUE(classEvent); + { + optional eventReset = classEvent->getMethod(event::str_reset); + ASSERT_TRUE(eventReset); + // 'Event::reset()' Method is non-const. + EXPECT_FALSE(eventReset->isConst()); + + auto [e0, r0] = eventReset->bind(event0).call(); + EXPECT_TRUE(e0 == error::ConstOverloadMissing); + ASSERT_TRUE(r0.isEmpty()); + + auto [e1, r2] = eventReset->bind(constCast(event0)).call(); + EXPECT_TRUE(e1 == error::IllegalConstCast); + ASSERT_TRUE(r2.isEmpty()); + } + + // RObject reflecting 'const Event&', storing pointer to reflected type internally, So just the + // address wrapped in std::any inside Robject is moved. Event's move constructor is not called. + RObject event1 = std::move(event0); + + ASSERT_FALSE(event1.isEmpty()); + EXPECT_FALSE(event1.isConstCastSafe()); + + // 'event0' must be empty now. + ASSERT_TRUE(event0.isEmpty()); + EXPECT_NE(event0.getTypeId(), event1.getTypeId()); + { + // Event::reset() is a non-const method. can't be called on const-object. + optional eventReset = classEvent->getMethod(event::str_reset); + ASSERT_TRUE(eventReset); + + // So here, call to 'non-const' method on 'const' target fails here. + auto [e0, r0] = eventReset->bind(event1).call(); + EXPECT_TRUE(e0 == error::ConstOverloadMissing); + ASSERT_TRUE(r0.isEmpty()); + + // Since the here, call to 'non-const' method on 'const' target fails here. + auto [e1, r2] = eventReset->bind(constCast(event1)).call(); + EXPECT_TRUE(e1 == error::IllegalConstCast); + ASSERT_TRUE(r2.isEmpty()); + } + } + // After move, these instance count must remain same. + EXPECT_TRUE(calender::get_instance_count() == 1); + // 'Calender' has 2 'Event' instances, shared_ptr and a std::unique_ptr. + EXPECT_TRUE(event::get_instance_count() == 2); + // 'Event' has a unique_ptr and two 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 2); + } + // After scope exit, stack instances are cleaned up automatically + EXPECT_TRUE(calender::get_instance_count() == 0); + EXPECT_TRUE(event::get_instance_count() == 0); + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(MoveSemantics, move_returned_RObject_reflecting_stack_object) + { + { + // Retrieve the reflected Record for the 'Calender' struct + optional classCalender = cxx::mirror().getRecord(calender::ns, calender::struct_); + ASSERT_TRUE(classCalender); + + optional createCalender = classCalender->getMethod(calender::str_create); + ASSERT_TRUE(createCalender); + + // Calender::create is a static method that returns stack-allocated Calender object. + // Calling this via reflection, moves the return value from Calender::create to here. + auto [err0, calender0] = (*createCalender)()(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(calender0.isEmpty()); + EXPECT_TRUE(calender0.isConstCastSafe()); + EXPECT_FALSE(calender0.isOnHeap()); + + EXPECT_TRUE(calender::get_instance_count() == 1); + // 'Calender' has 2 'Event' instances, shared_ptr and a std::unique_ptr. + EXPECT_TRUE(event::get_instance_count() == 2); + // 'Event' has a unique_ptr and two 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 2); + + // Sets Calender's move operation counter to zero + calender::reset_move_ops_counter(); + + // Moving a RObject created via alloc::Stack, invokes Calender's move constructor. + RObject calender1 = std::move(calender0); + + //TODO: Works on windows, fails on linux, differently optimized away for windows? + // Calender's move-constructor called once. + // EXPECT_TRUE(calender::get_move_ops_count() == 1); + + ASSERT_FALSE(calender1.isEmpty()); + EXPECT_TRUE(calender1.isConstCastSafe()); + EXPECT_FALSE(calender1.isOnHeap()); + + // 'calander0' must be empty now. + ASSERT_TRUE(calender0.isEmpty()); + EXPECT_NE(calender0.getTypeId(), calender1.getTypeId()); + + // After move, these instance count must remain same. + EXPECT_TRUE(calender::get_instance_count() == 1); + // 'Calender' has 2 'Event' instances, shared_ptr and a std::unique_ptr. + EXPECT_TRUE(event::get_instance_count() == 2); + // 'Event' has a unique_ptr and two 'Event' instances exists, So- + EXPECT_TRUE(date::get_instance_count() == 2); + } + // After scope exit, stack instances are cleaned up automatically + EXPECT_TRUE(calender::get_instance_count() == 0); + EXPECT_TRUE(event::get_instance_count() == 0); + EXPECT_TRUE(date::get_instance_count() == 0); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/FunctionalityTests/NameSpaceGlobalsTests.cpp b/RTLTestRunApp/src/FunctionalityTests/NameSpaceGlobalsTests.cpp new file mode 100644 index 00000000..705e5beb --- /dev/null +++ b/RTLTestRunApp/src/FunctionalityTests/NameSpaceGlobalsTests.cpp @@ -0,0 +1,298 @@ + +#include +#include + +#include "TestMirrorProvider.h" +#include "GlobalTestUtils.h" + +using namespace std; +using namespace rtl; + +using namespace test_utils; +using namespace test_mirror; + +namespace rtl_tests +{ + + TEST(Reflecting_pod, construct_char_on_heap_and_stack) + { + optional charType = cxx::mirror().getRecord(reflected_id::char_t); + ASSERT_TRUE(charType); + { + /* Attempting to construct a POD type('char') with a value directly via Record::create<>(). + Although the constructor for 'char' is registered, this call is resolved as if invoking + a copy constructor(signature: (const char&)), which is implicitly registered. + + Design Restriction : + - Direct invocation of copy constructors through Record::create<>() is intentionally disallowed. + - Copy construction is only permitted when cloning an existing reflected object + using RObject::clone<>(). + + Rationale : + - If the caller already knows the type 'T', there is no need to reflect its copy constructor + through create<>().A normal C++ copy(e.g., `T(other)`) is simpler and clearer. + - The only valid scenario for reflecting a copy constructor is when you are handling 'T' + as type-erased, for that, RTL provides rtl::reflect(..), which wraps an existing 'T' + into an RObject in a type-erased manner. (demonstrated in next test case.) + Therefore, this call yields 'SignatureMismatch' by design. + */ + auto [err, rchar] = charType->create('Q'); + EXPECT_TRUE(err == rtl::error::SignatureMismatch); + ASSERT_TRUE(rchar.isEmpty()); + } { + auto [err, rchar] = charType->create(); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(rchar.isEmpty()); + } + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + { + auto [err, rchar] = charType->create(); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(rchar.isEmpty()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + } + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(Reflecting_pod, construct_char_directly_and_clone) + { + //Now for cases, if you want to handle it type-erased and pass around. + RObject reflChar = rtl::reflect('Q'); + + error reterr = cxx::mirror().setupCloning(reflChar); + + ASSERT_TRUE(reterr == error::None); + { + //Internally calls the copy constructor. + auto [err, rchar] = reflChar.clone(); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(rchar.isEmpty()); + EXPECT_TRUE(rchar.canViewAs()); + + auto viewCh = rchar.view(); + ASSERT_TRUE(viewCh); + + char ch = viewCh->get(); + EXPECT_EQ(ch, 'Q'); + } + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + { + //Internally calls the copy constructor. + auto [err, rchar] = reflChar.clone(); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(rchar.isEmpty()); + + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + EXPECT_TRUE(rchar.canViewAs()); + + // Internally, RTL manages all Heap allocated objects with std::unique_ptr. + EXPECT_TRUE(rchar.canViewAs>()); + + auto viewCh = rchar.view(); + ASSERT_TRUE(viewCh); + + char ch = viewCh->get(); + EXPECT_EQ(ch, 'Q'); + } + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(RTLInterfaceCxxMirror, get_global_functions_with_wrong_names) + { + { + optional badFunc = cxx::mirror().getFunction("wrong_namespace", "wrong_function"); + EXPECT_FALSE(badFunc); + } { + optional badFunc = cxx::mirror().getFunction(str_complex, "wrong_function"); + EXPECT_FALSE(badFunc); + } { + optional badFunc = cxx::mirror().getFunction("wrong_getComplexNumAsString"); + EXPECT_FALSE(badFunc); + } + } + + + TEST(FunctionInNameSpace, get_namespace_function_types) + { + optional setReal = cxx::mirror().getFunction(str_complex, str_setReal); + ASSERT_TRUE(setReal); + + optional setImaginary = cxx::mirror().getFunction(str_complex, str_setImaginary); + ASSERT_TRUE(setImaginary); + + EXPECT_TRUE(setReal->getNamespace() == str_complex); + EXPECT_TRUE(setReal->getFunctionName() == str_setReal); + EXPECT_TRUE(setImaginary->getNamespace() == str_complex); + EXPECT_TRUE(setImaginary->getFunctionName() == str_setImaginary); + } + + + TEST(FunctionInNameSpace, namespace_function_execute_return) + { + optional getMagnitude = cxx::mirror().getFunction(str_complex, str_getMagnitude); + ASSERT_TRUE(getMagnitude); + + optional setReal = cxx::mirror().getFunction(str_complex, str_setReal); + ASSERT_TRUE(setReal); + + optional setImaginary = cxx::mirror().getFunction(str_complex, str_setImaginary); + ASSERT_TRUE(setImaginary); + + EXPECT_TRUE(setReal->hasSignature()); + + double real = g_real; //g_real's type is "const double", so can't be passed directly to setReal else, + //its type will be inferred 'const double' instead of 'double'. + auto [err0, ret0] = (*setReal)(real); + EXPECT_TRUE(err0 == rtl::error::None); + ASSERT_TRUE(ret0.isEmpty()); + + EXPECT_TRUE(setImaginary->hasSignature()); + + double imaginary = g_imaginary; //g_imaginary's type is "const double", so can't be passed directly to setImaginary else, + //its type will be inferred 'const double' instead of 'double'. + auto [err1, ret1] = (*setImaginary)(imaginary); + EXPECT_TRUE(err1 == rtl::error::None); + ASSERT_TRUE(ret1.isEmpty()); + + EXPECT_TRUE(getMagnitude->hasSignature<>()); //empty template params checks for zero arguments. + + auto [err2, ret2] = (*getMagnitude)(); + + EXPECT_TRUE(err2 == rtl::error::None); + ASSERT_FALSE(ret2.isEmpty()); + EXPECT_TRUE(ret2.canViewAs()); + + double retVal = ret2.view()->get(); + double magnitude = abs(complex(g_real, g_imaginary)); + EXPECT_DOUBLE_EQ(magnitude, retVal); + } + + + TEST(FunctionInNameSpace, execute_with_wrong_signature) + { + optional setReal = cxx::mirror().getFunction(str_complex, str_setReal); + ASSERT_TRUE(setReal); + + EXPECT_TRUE(setReal->hasSignature()); + EXPECT_FALSE(setReal->hasSignature()); + + //g_real's type is "const double", so can't be passed directly to setReal. + //Instead we can explicitly specify the types as template parameter, + //like, (*setReal).operator()(g_real); + //or we can use the bind<...>().call(), specifying type as template param, like, + auto [err, robj] = setReal->bind().call(g_real); + + EXPECT_TRUE(err == rtl::error::SignatureMismatch); + ASSERT_TRUE(robj.isEmpty()); + } + + + TEST(GlobalFunction, get_function_execute_return) + { + optional getComplexNumAsString = cxx::mirror().getFunction(str_getComplexNumAsString); + ASSERT_TRUE(getComplexNumAsString); + + auto [err, ret] = (*getComplexNumAsString)(); + + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + string retVal = ret.view()->get(); + string comlexNumStr = to_string(g_real) + "i" + to_string(g_imaginary); + EXPECT_TRUE(comlexNumStr == retVal); + } + + + TEST(GlobalFunction, overloaded_function_execute_return) + { + optional reverseString = cxx::mirror().getFunction(str_reverseString); + ASSERT_TRUE(reverseString); + { + //STRA's type is 'consexpr const char*', function accepts 'string', + //so type-casting in place as 'string' + auto [err, ret] = (*reverseString)(string(STRA)); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + string retVal = ret.view()->get(); + EXPECT_TRUE(retVal == STRA_REVERSE); + } { + //STRB's type is 'consexpr const char*', function accepts 'string', + //so explicitly binding type in template (using bind<...>()) to enforce the type as 'string'. + auto [err, ret] = reverseString->bind().call(STRB); + + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + string retVal = ret.view()->get(); + EXPECT_TRUE(retVal == STRB_REVERSE); + } { + auto [err, ret] = (*reverseString)(); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + string retVal = ret.view()->get(); + EXPECT_TRUE(retVal == REV_STR_VOID_RET); + } + } + + + TEST(Reflecting_STL_class, std_string__call_reflected_method) + { + optional stdStringClass = cxx::mirror().getRecord("std", "string"); + ASSERT_TRUE(stdStringClass); + + optional isStringEmpty = stdStringClass->getMethod("empty"); + ASSERT_TRUE(isStringEmpty); + + RObject reflected_str0 = rtl::reflect(std::string("")); //empty string. + { + auto [err, ret] = isStringEmpty->bind(reflected_str0).call(); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + EXPECT_TRUE(ret.view()->get()); + } + RObject reflected_str1 = rtl::reflect(std::string("not_empty")); + { + auto [err, ret] = isStringEmpty->bind(reflected_str1).call(); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + EXPECT_FALSE(ret.view()->get()); + } + } + + + TEST(Reflecting_STL_class, std_string_view__call_reflected_method) + { + optional stdStringClass = cxx::mirror().getRecord("std", "string_view"); + ASSERT_TRUE(stdStringClass); + + optional isStringEmpty = stdStringClass->getMethod("empty"); + ASSERT_TRUE(isStringEmpty); + + RObject reflected_str0 = rtl::reflect(""); //empty string. + { + auto [err, ret] = isStringEmpty->bind(reflected_str0).call(); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + EXPECT_TRUE(ret.view()->get()); + } + RObject reflected_str1 = rtl::reflect("not_empty"); + { + auto [err, ret] = isStringEmpty->bind(reflected_str1).call(); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + EXPECT_FALSE(ret.view()->get()); + } + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/FunctionalityTests/PerfectForwardingTests.cpp b/RTLTestRunApp/src/FunctionalityTests/PerfectForwardingTests.cpp new file mode 100644 index 00000000..ac4dd880 --- /dev/null +++ b/RTLTestRunApp/src/FunctionalityTests/PerfectForwardingTests.cpp @@ -0,0 +1,366 @@ +/** + * @file PerfectForwardingTests.cpp + * @brief This file contains unit tests to validate the behavior of perfect forwarding in the reflection system. + * + * Perfect forwarding ensures that arguments are forwarded to the correct method overload while preserving their + * value category (L-value, R-value, or const L-value). The tests use the reflection system to dynamically retrieve + * and invoke methods, ensuring that the correct overload is called based on the argument type and value category. + * + * Note: The explicitly provided template types (e.g., `std::string&`, `std::string&&`, `const std::string&`) are + * required by the design of the RTL to match the method signatures during invocation. + * + * Key Components: + * - `CxxMirror`: The main reflection interface that provides access to class metadata (`Record`) and methods (`Method`). + * - `Record`: Represents a reflected class/struct and provides access to its methods and constructors. + * - `Method`: Represents a reflected method and provides interfaces to invoke it dynamically. + * - `RObject`: A type-erased wrapper for return values and objects created via reflection, ensuring proper memory management. + */ + +#include + +#include "TestMirrorProvider.h" +#include "TestUtilsAnimal.h" + +using namespace std; +using namespace rtl; + +using namespace test_utils; +using namespace test_mirror; + +namespace rtl_tests +{ + /** + * @brief Test that an R-value reference binds only to the corresponding overload. + * + * This test verifies that the reflection system correctly identifies and invokes the method + * overload that accepts an R-value reference (`std::string&&`). + */ + TEST(PerfectForwardingTest, overload_resolution_with_rvalue_ref_on_heap_object) + { + { + // Retrieve the metadata for the "Animal" class. + optional classAnimal = cxx::mirror().getRecord(animal::class_); + ASSERT_TRUE(classAnimal); + + // Retrieve the "setAnimalName" method. + optional setAnimalName = classAnimal->getMethod(animal::str_setAnimalName); + ASSERT_TRUE(setAnimalName); + + // Create an instance of the "Animal" class. + auto [err0, animal] = classAnimal->create(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(animal.isEmpty()); + + // Verify that the method has the correct signature for an R-value reference. + const auto& isValid = setAnimalName->hasSignature(); + EXPECT_TRUE(isValid); + + // Invoke the method with an R-value reference. + auto [err1, ret1] = setAnimalName->bind(animal).call(animal::NAME); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret1.isEmpty()); + + // Validate the behavior of the method. + EXPECT_TRUE(animal::test_method_setAnimalName_rvalue_args(animal)); + } + + // Ensure that all instances are cleaned up. + EXPECT_TRUE(animal::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + /** + * @brief Test that a non-const L-value reference binds only to the corresponding overload. + * + * This test verifies that the reflection system correctly identifies and invokes the method + * overload that accepts a non-const L-value reference (`std::string&`). */ + TEST(PerfectForwardingTest, overload_resolution_with_non_const_lvaue_ref_on_heap_object) + { + { + // Retrieve the metadata for the "Animal" class. + optional classAnimal = cxx::mirror().getRecord(animal::class_); + ASSERT_TRUE(classAnimal); + + // Retrieve the "setAnimalName" method. + optional setAnimalName = classAnimal->getMethod(animal::str_setAnimalName); + ASSERT_TRUE(setAnimalName); + + // Create an instance of the "Animal" class. + auto [err0, animal] = classAnimal->create(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(animal.isEmpty()); + + // Verify that the method has the correct signature for a non-const L-value reference. + const auto& isValid = setAnimalName->hasSignature(); + EXPECT_TRUE(isValid); + + // Invoke the method with a non-const L-value reference. + auto nameStr = std::string(animal::NAME); + auto [err1, ret1] = setAnimalName->bind(animal).call(nameStr); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret1.isEmpty()); + + // Validate the behavior of the method. + EXPECT_TRUE(animal::test_method_setAnimalName_non_const_lvalue_ref_args(animal)); + } + + // Ensure that all instances are cleaned up. + EXPECT_TRUE(animal::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + /* + * @brief Test that a const L-value reference binds only to the corresponding overload. + * + * This test verifies that the reflection system correctly identifies and invokes the method + * overload that accepts a const L-value reference (`const std::string&`). */ + TEST(PerfectForwardingTest, overload_resolution_with_const_lvaue_ref_on_heap_object) + { + { + // Retrieve the metadata for the "Animal" class. + optional classAnimal = cxx::mirror().getRecord(animal::class_); + ASSERT_TRUE(classAnimal); + + // Retrieve the "setAnimalName" method. + optional setAnimalName = classAnimal->getMethod(animal::str_setAnimalName); + ASSERT_TRUE(setAnimalName); + + // Create an instance of the "Animal" class. + auto [err0, animal] = classAnimal->create(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(animal.isEmpty()); + + // Verify that the method has the correct signature for a const L-value reference. + const auto& isValid = setAnimalName->hasSignature(); + EXPECT_TRUE(isValid); + + // Invoke the method with a const L-value reference. + const auto nameStr = std::string(animal::NAME); + auto [err1, ret1] = setAnimalName->bind(animal).call(nameStr); + + EXPECT_TRUE(err1 == error::None); + EXPECT_TRUE(ret1.isEmpty()); + + // Validate the behavior of the method. + EXPECT_TRUE(animal::test_method_setAnimalName_const_lvalue_ref_args(animal)); + } + + // Ensure that all instances are cleaned up. + EXPECT_TRUE(animal::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + + /** + * @brief Test that an R-value reference binds only to the corresponding overload. + * + * This test verifies that the reflection system correctly identifies and invokes the method + * overload that accepts an R-value reference (`std::string&&`). + */ + TEST(PerfectForwardingTest, overload_resolution_with_rvalue_ref_on_stack_object) + { + { + // Retrieve the metadata for the "Animal" class. + optional classAnimal = cxx::mirror().getRecord(animal::class_); + ASSERT_TRUE(classAnimal); + + // Retrieve the "setAnimalName" method. + optional setAnimalName = classAnimal->getMethod(animal::str_setAnimalName); + ASSERT_TRUE(setAnimalName); + + // Create an instance of the "Animal" class. + auto [err0, animal] = classAnimal->create(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(animal.isEmpty()); + + // Verify that the method has the correct signature for an R-value reference. + const auto& isValid = setAnimalName->hasSignature(); + EXPECT_TRUE(isValid); + + // Invoke the method with an R-value reference. + auto [err1, ret1] = setAnimalName->bind(animal).call(animal::NAME); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret1.isEmpty()); + + // Validate the behavior of the method. + EXPECT_TRUE(animal::test_method_setAnimalName_rvalue_args(animal)); + } + + // Ensure that all instances are cleaned up. + EXPECT_TRUE(animal::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + /** + * @brief Test that a non-const L-value reference binds only to the corresponding overload. + * + * This test verifies that the reflection system correctly identifies and invokes the method + * overload that accepts a non-const L-value reference (`std::string&`). */ + TEST(PerfectForwardingTest, overload_resolution_with_non_const_lvaue_ref_on_stack_object) + { + { + // Retrieve the metadata for the "Animal" class. + optional classAnimal = cxx::mirror().getRecord(animal::class_); + ASSERT_TRUE(classAnimal); + + // Retrieve the "setAnimalName" method. + optional setAnimalName = classAnimal->getMethod(animal::str_setAnimalName); + ASSERT_TRUE(setAnimalName); + + // Create an instance of the "Animal" class. + auto [err0, animal] = classAnimal->create(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(animal.isEmpty()); + + // Verify that the method has the correct signature for a non-const L-value reference. + const auto& isValid = setAnimalName->hasSignature(); + EXPECT_TRUE(isValid); + + // Invoke the method with a non-const L-value reference. + auto nameStr = std::string(animal::NAME); + auto [err1, ret1] = setAnimalName->bind(animal).call(nameStr); + + EXPECT_TRUE(err1 == error::None); + ASSERT_TRUE(ret1.isEmpty()); + + // Validate the behavior of the method. + EXPECT_TRUE(animal::test_method_setAnimalName_non_const_lvalue_ref_args(animal)); + } + + // Ensure that all instances are cleaned up. + EXPECT_TRUE(animal::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + /* + * @brief Test that a const L-value reference binds only to the corresponding overload. + * + * This test verifies that the reflection system correctly identifies and invokes the method + * overload that accepts a const L-value reference (`const std::string&`). */ + TEST(PerfectForwardingTest, overload_resolution_with_const_lvaue_ref_on_stack_object) + { + { + // Retrieve the metadata for the "Animal" class. + optional classAnimal = cxx::mirror().getRecord(animal::class_); + ASSERT_TRUE(classAnimal); + + // Retrieve the "setAnimalName" method. + optional setAnimalName = classAnimal->getMethod(animal::str_setAnimalName); + ASSERT_TRUE(setAnimalName); + + // Create an instance of the "Animal" class. + auto [err0, animal] = classAnimal->create(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(animal.isEmpty()); + + // Verify that the method has the correct signature for a const L-value reference. + const auto& isValid = setAnimalName->hasSignature(); + EXPECT_TRUE(isValid); + + // Invoke the method with a const L-value reference. + const auto nameStr = std::string(animal::NAME); + auto [err1, ret1] = setAnimalName->bind(animal).call(nameStr); + + EXPECT_TRUE(err1 == error::None); + EXPECT_TRUE(ret1.isEmpty()); + + // Validate the behavior of the method. + EXPECT_TRUE(animal::test_method_setAnimalName_const_lvalue_ref_args(animal)); + } + + // Ensure that all instances are cleaned up. + EXPECT_TRUE(animal::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(PerfectForwardingTest, static_fn_overload_resolution_with_rvalue_ref) + { + { + optional classAnimal = cxx::mirror().getRecord(animal::class_); + ASSERT_TRUE(classAnimal); + + optional updateZooKeeper = classAnimal->getMethod(animal::str_updateZooKeeper); + ASSERT_TRUE(updateZooKeeper); + + const auto& isValid = updateZooKeeper->hasSignature(); + EXPECT_TRUE(isValid); + + auto [err, ret] = updateZooKeeper->bind().call(animal::ZOO_KEEPER); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + const string& retStr = ret.view()->get(); + EXPECT_TRUE(animal::test_method_updateZooKeeper(retStr)); + } + + EXPECT_TRUE(animal::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(PerfectForwardingTest, static_fn_overload_resolution_with_const_lvalue_ref) + { + { + optional classAnimal = cxx::mirror().getRecord(animal::class_); + ASSERT_TRUE(classAnimal); + + optional updateZooKeeper = classAnimal->getMethod(animal::str_updateZooKeeper); + ASSERT_TRUE(updateZooKeeper); + + const auto& isValid = updateZooKeeper->hasSignature(); + EXPECT_TRUE(isValid); + + const auto zookeeper = std::string(animal::ZOO_KEEPER); + auto [err, ret] = updateZooKeeper->bind().call(zookeeper); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + const string& retStr = ret.view()->get(); + EXPECT_TRUE(animal::test_method_updateZooKeeper(retStr)); + } + + EXPECT_TRUE(animal::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(PerfectForwardingTest, static_fn_overload_resolution_with_non_const_lvalue_ref) + { + { + optional classAnimal = cxx::mirror().getRecord(animal::class_); + ASSERT_TRUE(classAnimal); + + optional updateZooKeeper = classAnimal->getMethod(animal::str_updateZooKeeper); + ASSERT_TRUE(updateZooKeeper); + + const auto& isValid = updateZooKeeper->hasSignature(); + EXPECT_TRUE(isValid); + + auto zookeeper = std::string(animal::ZOO_KEEPER); + auto [err, ret] = updateZooKeeper->bind().call(zookeeper); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + const string& retStr = ret.view()->get(); + EXPECT_TRUE(animal::test_method_updateZooKeeper(retStr)); + } + + EXPECT_TRUE(animal::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/FunctionalityTests/ReflectionOpErrorCodeTests.cpp b/RTLTestRunApp/src/FunctionalityTests/ReflectionOpErrorCodeTests.cpp new file mode 100644 index 00000000..0fbbd762 --- /dev/null +++ b/RTLTestRunApp/src/FunctionalityTests/ReflectionOpErrorCodeTests.cpp @@ -0,0 +1,331 @@ + +/* +* +* Below error codes are covered in ConstMethodOverloadTests.cpp +* rtl::error::IllegalConstCast +* rtl::error::AmbiguousConstOverload +* rtl::error::ConstOverloadMissing +* rtl::error::NonConstOverloadMissing +* rtl::error::ConstCallViolation +* and, +* rtl::error::FunctionNotRegistered, is not internally used by RTL. +* Function/Method objects are returned wrapped in std::optional<>, which will +* be empty if its not in registered in Reflection-system. +* +*/ + +#include + +#include "TestMirrorProvider.h" +#include "TestUtilsBook.h" +#include "TestUtilsDate.h" +#include "TestUtilsPerson.h" + +using namespace std; +using namespace rtl; + +using namespace test_utils; +using namespace test_mirror; + +namespace rtl_tests +{ + TEST(ReflectionOperationStatus, error_EmptyRObject) + { + { + RObject emptyObj; + ASSERT_TRUE(emptyObj.isEmpty()); + { + auto [err, person] = emptyObj.clone(); + EXPECT_TRUE(err == error::EmptyRObject); + ASSERT_TRUE(person.isEmpty()); + } { + auto [err, person] = emptyObj.clone(); + EXPECT_TRUE(err == error::EmptyRObject); + ASSERT_TRUE(person.isEmpty()); + } + } + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ReflectionOperationStatus, error_TypeNotDefaultConstructible) + { + optional classEvent = cxx::mirror().getRecord(event::ns, event::struct_); + ASSERT_TRUE(classEvent); + + auto [err0, robj0] = classEvent->create(); + + EXPECT_TRUE(err0 == error::TypeNotDefaultConstructible); + ASSERT_TRUE(robj0.isEmpty()); + + auto [err1, robj1] = classEvent->create(); + + EXPECT_TRUE(err1 == error::TypeNotDefaultConstructible); + ASSERT_TRUE(robj1.isEmpty()); + } + + + TEST(ReflectionOperationStatus, error_ReflectedObjectIsNotInWrapper) + { + char ch = 'R'; + RObject rCh = rtl::reflect(ch); + + error reterr = cxx::mirror().setupCloning(rCh); + ASSERT_TRUE(reterr == error::None); + + EXPECT_FALSE(rCh.isAllocatedByRtl()); + { + auto [err, rch] = rCh.clone(); + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(rch.isEmpty()); + EXPECT_TRUE(rch.canViewAs()); + EXPECT_EQ(rch.view()->get(), 'R'); + } + EXPECT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + { + auto [err, rch] = rCh.clone(); + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(rch.isEmpty()); + EXPECT_TRUE(rch.canViewAs()); + EXPECT_EQ(rch.view()->get(), 'R'); + EXPECT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + } + EXPECT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + { + auto [err, rch] = rCh.clone(); + EXPECT_TRUE(err == error::NotWrapperType); + ASSERT_TRUE(rch.isEmpty()); + /* this will not compile, fail with message - + static_assert failed: 'Heap allocation forbidden for STL-Wrappers (e.g. smart pointers/optionals/reference_wrappers).' */ + // auto [err0, rch0] = rChptr.clone(); + } + } + + + TEST(ReflectionOperationStatus, std_unique_ptr__error_TypeNotCopyConstructible) + { + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + std::unique_ptr chPtr = std::make_unique('R'); + + { + RObject rChptr = rtl::reflect(chPtr); + + ASSERT_FALSE(rChptr.isEmpty()); + EXPECT_FALSE(rChptr.isAllocatedByRtl()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + + error reterr = cxx::mirror().setupCloning(rChptr); + ASSERT_TRUE(reterr == error::None); + + EXPECT_TRUE(rChptr.canViewAs()); + { + auto viewCh = rChptr.view(); + ASSERT_TRUE(viewCh); + + char ch = viewCh->get(); + EXPECT_EQ(ch, 'R'); + } { + //Try to create copy of std::unique_ptr on stack. + auto [err, rch0] = rChptr.clone(); + EXPECT_TRUE(err == error::TypeNotCopyConstructible); + } { + // Try to create copy of std::unique_ptr explicitly on stack. + auto [err, rch0] = rChptr.clone(); + EXPECT_TRUE(err == error::TypeNotCopyConstructible); + } { + // Try to create copy of std::unique_ptr on heap. + auto [err, rch0] = rChptr.clone(); + EXPECT_TRUE(err == error::StlWrapperHeapAllocForbidden); + } { + // Now try to create copy of std::unique_ptr explicitly on heap. + auto [err, rch0] = rChptr.clone(); + EXPECT_TRUE(err == error::StlWrapperHeapAllocForbidden); + } { + // but we can definitly create the copy of underlying value. + auto [err, rch0] = rChptr.clone(); + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(rch0.isEmpty()); + EXPECT_TRUE(rch0.canViewAs()); + + auto viewCh = rChptr.view(); + ASSERT_TRUE(viewCh); + + char ch = viewCh->get(); + EXPECT_EQ(ch, 'R'); + } + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + { + // but we can definitly create the copy of underlying value. + auto [err, rch0] = rChptr.clone(); + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(rch0.isEmpty()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 2); + EXPECT_TRUE(rch0.canViewAs()); + + auto viewCh = rChptr.view(); + ASSERT_TRUE(viewCh); + + char ch = viewCh->get(); + EXPECT_EQ(ch, 'R'); + } + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + } + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ReflectionOperationStatus, copy_construct__error_TypeNotCopyConstructible) + { + { + optional classCalender = cxx::mirror().getRecord(calender::ns, calender::struct_); + ASSERT_TRUE(classCalender); + + //Events's constructor not registered, get its instance from 'Calander'. + optional getEvent = classCalender->getMethod(calender::str_getTheEvent); + ASSERT_TRUE(getEvent); + + // Create Calender, which will create a Event's instance. + auto [err0, calender] = classCalender->create(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(calender.isEmpty()); + + // Get the Event's instance. + auto [err1, event] = getEvent->bind(calender).call(); + EXPECT_TRUE(err1 == error::None); + ASSERT_FALSE(event.isEmpty()); + + // Try to call copy-constructor of class Event. + auto [err2, eventCp0] = event.clone(); + + EXPECT_TRUE(err2 == error::CloningDisabled); + ASSERT_TRUE(eventCp0.isEmpty()); + + error reterr = cxx::mirror().setupCloning(event); + ASSERT_TRUE(reterr == error::None); + + // Try to call copy-constructor of class Event. + auto [err3, eventCp1] = event.clone(); + + // Cannot create heap instance: Calender's copy constructor is deleted. + EXPECT_TRUE(err3 == error::TypeNotCopyConstructible); + ASSERT_TRUE(eventCp1.isEmpty()); + } + EXPECT_TRUE(calender::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ReflectionOperationStatus, alloc_on_stack__error_TypeNotCopyConstructible) + { + { + // Fetch the reflected Record for class 'Library'. + optional classLibrary = cxx::mirror().getRecord(library::class_); + ASSERT_TRUE(classLibrary); + { + // Attempt to create a reflected instance allocated on the heap. + auto [err, robj] = classLibrary->create(); + /* Heap allocation succeeds: + * Even though Library's copy constructor is deleted, RObject internally stores + * the pointer directly inside std::any (type-erased), without requiring the type T + * to be copy-constructible. + */ EXPECT_TRUE(err == error::None); + ASSERT_FALSE(robj.isEmpty()); + } + // Ensure no leaked or lingering reflected instances. + EXPECT_TRUE(library::assert_zero_instance_count()); + { + // Attempt to create a reflected instance allocated on the stack. + auto [err, robj] = classLibrary->create(); + /* Stack allocation fails: + * Creating a stack instance requires storing the actual object inside std::any. + * Since std::any requires the contained type T to be copy-constructible for emplacement, + * and Library's copy constructor is deleted, construction fails. + */ EXPECT_TRUE(err == error::TypeNotCopyConstructible); + ASSERT_TRUE(robj.isEmpty()); + } + } + } + + + TEST(ReflectionOperationStatus, static_method_call__error_SignatureMismatch) + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional getProfile = classPerson->getMethod(person::str_getProfile); + ASSERT_TRUE(getProfile); + EXPECT_TRUE(getProfile->hasSignature<>()); //empty template params checks for zero arguments. + + auto [err, robj] = getProfile->bind().call(std::string()); + + EXPECT_TRUE(err == error::SignatureMismatch); + ASSERT_TRUE(robj.isEmpty()); + } + + + TEST(ReflectionOperationStatus, method_call__error_EmptyRObject) + { + { + RObject emptyObj; + ASSERT_TRUE(emptyObj.isEmpty()); + + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err, ret] = classBook->getMethod(book::str_getPublishedOn)->bind(emptyObj).call(); + EXPECT_TRUE(err == error::EmptyRObject); + ASSERT_TRUE(ret.isEmpty()); + } + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ReflectionOperationStatus, method_call_using_heap_object__error_TargetMismatch) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err0, person] = classPerson->create(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + + optional getPublishedOn = classBook->getMethod(book::str_getPublishedOn); + ASSERT_TRUE(getPublishedOn); + + auto [err1, ret] = getPublishedOn->bind(person).call(); + EXPECT_TRUE(err1 == error::TargetMismatch); + ASSERT_TRUE(ret.isEmpty()); + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(ReflectionOperationStatus, method_call_using_stack_object__error_TargetMismatch) + { + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional classBook = cxx::mirror().getRecord(book::class_); + ASSERT_TRUE(classBook); + + auto [err0, person] = classPerson->create(); + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + + optional getPublishedOn = classBook->getMethod(book::str_getPublishedOn); + ASSERT_TRUE(getPublishedOn); + + auto [err1, ret] = getPublishedOn->bind(person).call(); + EXPECT_TRUE(err1 == error::TargetMismatch); + ASSERT_TRUE(ret.isEmpty()); + } + EXPECT_TRUE(person::assert_zero_instance_count()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/FunctionalityTests/ReturnValueReflectionTest.cpp b/RTLTestRunApp/src/FunctionalityTests/ReturnValueReflectionTest.cpp new file mode 100644 index 00000000..61bc03ed --- /dev/null +++ b/RTLTestRunApp/src/FunctionalityTests/ReturnValueReflectionTest.cpp @@ -0,0 +1,78 @@ + +#include + +#include "TestMirrorProvider.h" +#include "TestUtilsDate.h" +//#include "TestUtilsBook.h" +//#include "GlobalTestUtils.h" + +using namespace test_utils; +using namespace test_mirror; + +namespace rtl_tests +{ + TEST(ReflecetdReturnValues, on_registered_return_type__test_cloning) + { + //I don't know if the 'Event' is class or struct..Reflection YaY!. :P + auto classEvent = cxx::mirror().getRecord(reflected_id::event); + ASSERT_TRUE(classEvent); + + auto [err0, robj0] = classEvent->create(); + + //Event's constructor is private, not accessible, Hence the error. + EXPECT_TRUE(err0 == rtl::error::TypeNotDefaultConstructible); + ASSERT_TRUE(robj0.isEmpty()); + { + auto classCalender = cxx::mirror().getRecord(reflected_id::calender); + ASSERT_TRUE(classCalender); + + auto [err1, calender] = classCalender->create(); + + EXPECT_TRUE(err1 == rtl::error::None); + ASSERT_FALSE(calender.isEmpty()); + + // 'Calender' instance created. + EXPECT_TRUE(calender::get_instance_count() == 1); + // 'Calender' has two 'Event' instances. + EXPECT_TRUE(event::get_instance_count() == 2); + + // Event's object can be obtained from Calender's object ('Calander' has-a 'Event'). + auto getEvent = classCalender->getMethod(calender::str_getTheEvent); + ASSERT_TRUE(getEvent); + + // get the Event's object from the 'Calender' object. + auto [err2, event] = getEvent->bind(calender).call(); + EXPECT_TRUE(err2 == rtl::error::None); + ASSERT_FALSE(event.isEmpty()); + EXPECT_TRUE(event.getTypeId() == reflected_id::event); + { + { + auto [err, robj] = event.clone(); + EXPECT_TRUE(err == rtl::error::CloningDisabled); + } + + rtl::error reterr = cxx::mirror().setupCloning(event); + ASSERT_TRUE(reterr == rtl::error::None); + + { + auto [err, robj] = event.clone(); + //Event's copy-constructor private or deleted. + EXPECT_TRUE(err == rtl::error::TypeNotCopyConstructible); + ASSERT_TRUE(robj.isEmpty()); + // Two 'Event' instances, owned by 'Calender' + EXPECT_TRUE(event::get_instance_count() == 2); + } + } { + auto [err, robj] = event.clone(); + //Event's copy-constructor private or deleted. + EXPECT_TRUE(err == rtl::error::TypeNotCopyConstructible); + ASSERT_TRUE(robj.isEmpty()); + // Still, two 'Event' instances, owned by 'Calender' + EXPECT_TRUE(event::get_instance_count() == 2); + } + } + EXPECT_TRUE(calender::assert_zero_instance_count()); + //Once 'Calender' is destroyed, all 'Event's should too. + ASSERT_TRUE(event::assert_zero_instance_count()); + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/FunctionalityTests/StaticMethodTests.cpp b/RTLTestRunApp/src/FunctionalityTests/StaticMethodTests.cpp new file mode 100644 index 00000000..5d93608f --- /dev/null +++ b/RTLTestRunApp/src/FunctionalityTests/StaticMethodTests.cpp @@ -0,0 +1,183 @@ + +#include + +#include "TestMirrorProvider.h" +#include "TestUtilsPerson.h" + +using namespace std; +using namespace rtl; + +using namespace test_utils; +using namespace test_mirror; + +namespace rtl_tests +{ + TEST(StaticMethods, unique_method_call) + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional getDefaults = classPerson->getMethod(person::str_getDefaults); + ASSERT_TRUE(getDefaults); + EXPECT_TRUE(getDefaults->hasSignature<>()); //empty template params checks for zero arguments. + + auto [err, ret] = (*getDefaults)()(); + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + const string& retStr = ret.view()->get(); + EXPECT_EQ(retStr, person::get_str_returned_on_call_getDefaults()); + } + + + TEST(StaticMethods, overload_method_void_call) + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional getProfile = classPerson->getMethod(person::str_getProfile); + ASSERT_TRUE(getProfile); + EXPECT_TRUE(getProfile->hasSignature<>()); //empty template params checks for zero arguments. + + auto [err, ret] = getProfile->bind().call(); + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + const string& retStr = ret.view()->get(); + EXPECT_EQ(retStr, person::get_str_returned_on_call_getProfile()); + } + + + TEST(StaticMethods, overload_method_args_bool_call) + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional getProfile = classPerson->getMethod(person::str_getProfile); + ASSERT_TRUE(getProfile); + EXPECT_TRUE(getProfile->hasSignature()); + { + auto [err, ret] = (*getProfile)()(true); + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + const string& retStr = ret.view()->get(); + EXPECT_EQ(retStr, person::get_str_returned_on_call_getProfile(true)); + } { + //use the bind-call syntax. + auto [err, ret] = getProfile->bind().call(false); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + const string& retStr = ret.view()->get(); + EXPECT_EQ(retStr, person::get_str_returned_on_call_getProfile(false)); + } + } + + + TEST(StaticMethods, overload_method_args_string_size_t_call) + { + optional recOpt = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(recOpt.has_value()); + + const Record& classPerson = recOpt.value(); + optional methOpt = classPerson.getMethod(person::str_getProfile); + ASSERT_TRUE(methOpt.has_value()); + + const Method& getProfile = methOpt.value(); + EXPECT_TRUE((getProfile.hasSignature())); + + size_t age = person::AGE; + string occupation = person::OCCUPATION; + auto [err, ret] = getProfile.bind().call(occupation, age); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + const string& retStr = ret.view()->get(); + const string& checkStr = person::get_str_returned_on_call_getProfile(); + + EXPECT_EQ(retStr, checkStr); + } + + + TEST(StaticMethods, static_method_call_on_target_instance) + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + optional getDefaults = classPerson->getMethod(person::str_getDefaults); + ASSERT_TRUE(getDefaults); + EXPECT_TRUE(getDefaults->hasSignature<>()); //empty template params checks for zero arguments. + + auto [err0, person] = classPerson->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + { + auto [err, ret] = (*getDefaults)(person)(); + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + auto& retStr = ret.view()->get(); + EXPECT_EQ(retStr, person::get_str_returned_on_call_getDefaults()); + } { + auto [err, ret] = getDefaults->bind(person).call(); + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + auto& retStr = ret.view()->get(); + EXPECT_EQ(retStr, person::get_str_returned_on_call_getDefaults()); + } + } + + + TEST(StaticMethods, static_method_call_on_target_instance_with_args) + { + optional classPerson = cxx::mirror().getRecord(person::class_); + ASSERT_TRUE(classPerson); + + auto [err0, person] = classPerson->create(); + + EXPECT_TRUE(err0 == error::None); + ASSERT_FALSE(person.isEmpty()); + + optional getProfile = classPerson->getMethod(person::str_getProfile); + ASSERT_TRUE(getProfile); + EXPECT_TRUE((getProfile->hasSignature())); + + size_t age = person::AGE; + string occupation = person::OCCUPATION; + { + auto [err, ret] = getProfile->bind(person).call(occupation, age); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + const string& retStr = ret.view()->get(); + const string& checkStr = person::get_str_returned_on_call_getProfile(); + + EXPECT_EQ(retStr, checkStr); + } { + auto [err, ret] = (*getProfile)(person)(occupation, age); + + EXPECT_TRUE(err == error::None); + ASSERT_FALSE(ret.isEmpty()); + EXPECT_TRUE(ret.canViewAs()); + + const string& retStr = ret.view()->get(); + const string& checkStr = person::get_str_returned_on_call_getProfile(); + + EXPECT_EQ(retStr, checkStr); + } + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/MyReflectionTests/MyCxxMirrorProvider.cpp b/RTLTestRunApp/src/MyReflectionTests/MyCxxMirrorProvider.cpp new file mode 100644 index 00000000..d406e941 --- /dev/null +++ b/RTLTestRunApp/src/MyReflectionTests/MyCxxMirrorProvider.cpp @@ -0,0 +1,184 @@ + +#include "RTLibInterface.h" +#include "MyReflectingType.h" + +namespace my_type +{ + const rtl::CxxMirror& MyReflection() + { + static auto cxx_mirror = rtl::CxxMirror( { + + /* Register a free(C - style) function within a namespace. + If registered with a namespace, it must also be specified when querying: + cxx_mirror().getFunction("ext", "sendString") + Note: when registering free functions, the '&' operator is not required + when passing the function pointer to build(). + */ rtl::type().ns("ext").function("sendString").build(ext::sendString), + + + /* Another free (C-style) function inside a namespace. + This example demonstrates overloaded function registration. + Available overloads are: + void sendAsString(Person) + void sendAsString(Person&&) + void sendAsString(const char*) + + Since multiple overloads exist, the compiler cannot automatically deduce + the correct function pointer. Therefore, the parameter type must be explicitly + specified with `.function<>()`. + + This guides `.build()` to correctly resolve the intended overload. + Omitting the template type will result in a compile-time error. + */ rtl::type().ns("ext").function("sendAsString").build(ext::sendAsString), + + + /* Next overload registration: + void sendAsString(Person) + As with other overloads, the signature must be explicitly specified + so that `.build()` can select the correct function pointer. + */ rtl::type().ns("ext").function("sendAsString").build(ext::sendAsString), + + + /* And finally, the overload with an rvalue parameter: + void sendAsString(Person&&) + Again, the signature must be explicitly specified + to ensure `.build()` resolves to the correct function pointer. + */ rtl::type().ns("ext").function("sendAsString").build(ext::sendAsString), + + + /* Register a class/struct type without a namespace. + Since no namespace is provided, it will be queried directly by name, e.g.: + cxx_mirror().getRecord("Person"); + + This registration implicitly adds the default constructor, copy constructor, + and destructor. Explicitly registering these members is not allowed and + will result in a compile-time error. + + The order of registration does not matter- the type can be registered before + or after its members. However, the type itself must be registered; otherwise, + any attempted member registrations will be ignored and a warning will be + displayed on the console. + */ rtl::type().record("Person").build(), + + // rtl::type().member().constructor().build(), // Default constructor, will not compile. + // rtl::type().member().constructor().build(), // Copy constructor, will not compile. + // rtl::type().member().constructor().build(), // Move constructor, will not compile. + + + /* Legal registration of an overloaded constructor. + In this case, there are two possible overloads: `std::string&` and `const std::string&`. + + Note that we do not specify `&` in the template parameter. When calling this constructor + reflectively with a `std::string`, the argument is forwarded as a universal reference (&&). + By C++ rules, this binds naturally to `const std::string&`, without requiring any special + handling in RTL. + + If the class provides only a `std::string&` constructor (and no `const std::string&`), + then this registration will fail to compile. Similarly, if three overloads exist- + `std::string`, `std::string&`, and `const std::string&`- the compiler itself will report + an ambiguity error. + + You may explicitly register with `std::string&` or `const std::string&`, but RTL will + normalize types by stripping `const` and reference qualifiers during registration. + */ rtl::type().member().constructor().build(), + + + /* Registers a regular non-const member-function. + This function can only be called on a non-const `Person` object. + Attempting to call it on a true-const `Person` object will result in `error::ConstCallViolation`. + See test case: `non_const_method_semantics__on_true_const_target`. + `non_const_method_semantics__on_logical_const_target` + */ rtl::type().member().method("getName").build(&Person::getName), + + + /* Registering a static member-function. + Must be registered via `.methodStatic()`, otherwise it is a compile-time error. + `.methodStatic()` restricts `build()` to only accept static member-function pointers. + + Runtime semantics: + Static methods are independent of object state, so they can always be invoked + regardless of whether the reflected context is const or non-const. + */ rtl::type().member().methodStatic("getDefaults").build(&Person::getDefaults), + + + /* Registering a non-const member-function. + Must be registered via `.method()`, otherwise it is a compile-time error. + The non-const overload of `updateAddress` is automatically selected here, because + `.method()` restricts `build()` to only accept non-const member-function pointers. + + If multiple overloads are available, the correct one is resolved at runtime. + See test case: `const_based_overload_resolution_semantics__on_true_const_target` & + `const_based_overload_resolution_semantics__on_logical_const_target` + */ rtl::type().member().method("updateAddress").build(&Person::updateAddress), + + + /* Registering a const member-function. + Must be registered via `.methodConst()`, otherwise it is a compile-time error. + The const overload of `updateAddress` is automatically selected here, because + `.methodConst()` restricts `build()` to only accept const member-function pointers. + + If multiple overloads are available, the correct one is resolved at runtime. + See test case: `const_based_overload_resolution_semantics__on_true_const_target` & + `const_based_overload_resolution_semantics__on_logical_const_target` + */ rtl::type().member().methodConst("updateAddress").build(&Person::updateAddress), + + + + /* Registers the member function `setTitle`, which only accepts an rvalue reference (`std::string&&`). + To invoke this method reflectively, the argument type `std::string&&` must be explicitly specified. + See test case: `perfect_forwarding_seamantics__rvalue_ref`. + */ rtl::type().member().method("setTitle").build(&Person::setTitle), + + + /* Registers the overloaded member function `setOccupation` that accepts an rvalue-reference (`std::string&&`). + Since this method has multiple overloads, RTL cannot automatically deduce the correct one (unlike `setTitle`, + which had no overloads). Therefore, we must explicitly specify the rvalue-ref type in the `method` template parameter. + For overload resolution, see test case: `perfect_forwarding_semantics__overload_resolution`. + */ rtl::type().member().method("setOccupation").build(&Person::setOccupation), + + + /* Registers the other overloaded version of `setOccupation` that accepts a const-lvalue-reference (`const std::string&`). + Similar to the rvalue-ref case, this overload cannot be picked automatically, so we explicitly specify the + `const std::string&` type in the `method` template parameter. + For overload resolution, see test case: `perfect_forwarding_semantics__overload_resolution`. + */ rtl::type().member().method("setOccupation").build(&Person::setOccupation), + + + /* The method `setProfile` has two overloads. + To register one, you must explicitly specify the parameter type in the template argument. + For example: `method(...)`. Without this, compilation will fail. + Note: overload resolution happens at runtime (see test cases `overload_resolution_semantics__*`). + */ rtl::type().member().method("setProfile").build(&Person::setProfile), + + + /* Example to illustrate overload behavior: + + Person person("Tim"); + person.setProfile(std::string("Tim's prof")); + - This compiles fine, as it binds to `setProfile(std::string)` + (the version taking the argument by value). + + std::string profStr = "Tim's profile"; + person.setProfile(profStr); + - This does not compile, because `profStr` is an lvalue. + It could bind to either `setProfile(std::string)` or + `setProfile(std::string&)`, creating ambiguity. + + However, RTL can successfully register both overloads by explicitly specifying + the reference type in `method()`s template parameter, e.g. `method(...)`. + Overload resolution still occurs at runtime. See test case `overload_resolution__setProfile`, + which shows that even when explicitly registering the `std::string&` overload, a reflective call + with an rvalue will still resolve to the `std::string` (by value) version, because that is the + only syntactically valid match. + */ rtl::type().member().method("setProfile").build(&Person::setProfile), + + + /* The method `getProfile` has only 'const' version, No non-const overload. + Must be registered via `.methodConst()`, otherwise it is a compile-time error. + Note: overload resolution happens at runtime (see test case `overload_resolution__setProfile`). + */ rtl::type().member().methodConst("getProfile").build(&Person::getProfile), + }); + + return cxx_mirror; + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/MyReflectionTests/MyReflectingType.h b/RTLTestRunApp/src/MyReflectionTests/MyReflectingType.h new file mode 100644 index 00000000..b6c8554b --- /dev/null +++ b/RTLTestRunApp/src/MyReflectionTests/MyReflectingType.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +namespace my_type +{ + struct Person + { + const std::string name; + + Person(std::string& pName) : name(pName) {} + + Person(const std::string& pName) : name(pName) {} + + std::string getName() { return ("called_non_const__" + name); } + + std::string setTitle(std::string&&) { return "called_by_ref_rvalue"; } + + std::string updateAddress() { return "called_non_const_overload"; } + + std::string updateAddress() const { return "called_const_overload"; } + + std::string setProfile(std::string pProfStr) { return "called_by_val"; } + + std::string setProfile(std::string& pProfStr) { return "called_by_ref"; } + + std::string getProfile() const { return "only_const_method_version_exists"; } + + std::string setOccupation(std::string&& pProfStr) { return "called_by_rvalue_ref"; } + + std::string setOccupation(const std::string& pProfStr) { return "called_by_ref_lvalue"; } + + static std::string getDefaults() { return "Person_defaults_returned"; } + }; + + + namespace ext { + + static std::string sendString(std::string pString) { return ("sent_string_" + pString); } + + static std::string sendAsString(Person pPerson) { return "sent_string_lvalue_" + pPerson.name; } + + static std::string sendAsString(Person&& pPerson) { return "sent_string_rvalue_" + pPerson.name; } + + static std::string sendAsString(const char* pCString) { return "sent_string_literal_" + std::string(pCString); } + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/MyReflectionTests/MyReflectionTests.cpp b/RTLTestRunApp/src/MyReflectionTests/MyReflectionTests.cpp new file mode 100644 index 00000000..349c1836 --- /dev/null +++ b/RTLTestRunApp/src/MyReflectionTests/MyReflectionTests.cpp @@ -0,0 +1,658 @@ + +#include + +#include "RTLibInterface.h" +#include "MyReflectingType.h" + +using namespace my_type; + +namespace my_type { extern const rtl::CxxMirror& MyReflection(); } + +namespace +{ + TEST(MyReflectionTests, invoking_semantics__C_style_function_with_no_overload) + { + { + // Attempt to retrieve the C-style function without specifying a namespace. + std::optional sendString = MyReflection().getFunction("sendString"); + // Not found, since it was registered under the 'ext' namespace. + EXPECT_FALSE(sendString); + } { + // Retrieve the function with its correct namespace. + std::optional sendString = MyReflection().getFunction("ext", "sendString"); + // Found successfully. + ASSERT_TRUE(sendString); + + auto theStr = std::string("Initiating reflection tests."); + auto expectReturnStr = ("sent_string_" + theStr); + + // Nothing to bind here, since this is a non-member (C-style) function. + // However, if the function takes reference parameters that require perfect forwarding, + // the binding can be specified explicitly using `bind()`, `bind()`, or `bind()`. + // In essence, `bind()` enables correct forwarding semantics for function calls. + auto [err, ret] = sendString->bind().call(theStr); + + // Reflected call executes successfully. + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + // We know the return type is std::string. + EXPECT_TRUE(ret.canViewAs()); + + // Extract the std::string view from `ret`. + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + // Confirms that the expected function was invoked. + EXPECT_EQ(retStr, expectReturnStr); + } + } + + + TEST(MyReflectionTests, overload_resolution_semantics__arg_const_char_ptr) + { + // Retrieve the function with its correct namespace. + std::optional sendAsString = MyReflection().getFunction("ext", "sendAsString"); + // Found successfully. + ASSERT_TRUE(sendAsString); + + auto theStr = std::string("const_char_ptr."); + auto expectReturnStr = ("sent_string_literal_" + theStr); + + // Nothing to bind here, since this is a non-member (C-style) function and it does not + // require arguments to be perfectly forwarded. + // The argument passed is `const char*`, and the corresponding overload has been registered. + // The reflective call succeeds. If a mismatched argument is passed, + // `error::SignatureMismatch` will be returned. + auto [err, ret] = sendAsString->bind().call(theStr.c_str()); + + // Reflected call executes successfully. + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + // We know the return type is std::string. + EXPECT_TRUE(ret.canViewAs()); + + // Extract the std::string view from `ret`. + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + // Confirms that the expected function was invoked. + EXPECT_EQ(retStr, expectReturnStr); + } + + + TEST(MyReflectionTests, overload_resolution_semantics__arg_lvalue) + { + // Retrieve the function from its namespace. + std::optional sendAsString = MyReflection().getFunction("ext", "sendAsString"); + ASSERT_TRUE(sendAsString); // Function found successfully. + + auto nameStr = std::string("person_Eric"); + auto person = Person(nameStr); + auto expectReturnStr = ("sent_string_lvalue_" + nameStr); + + // Nothing to bind here: the call is with a regular lvalue. + // This resolves to the overload `sendAsString(Person)`. + // The overload was registered, so the reflective call will succeed. + // If the argument type mismatches, `error::SignatureMismatch` will be returned. + auto [err, ret] = sendAsString->bind().call(person); + + // Validate reflective call succeeded. + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + // Verify return type and extract result. + EXPECT_TRUE(ret.canViewAs()); + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + // Confirms the correct overload was invoked. + EXPECT_EQ(retStr, expectReturnStr); + } + + + TEST(MyReflectionTests, overload_resolution_with_perfect_forwarding_semantics__arg_rvalue) + { + // Retrieve the function from its namespace. + std::optional sendAsString = MyReflection().getFunction("ext", "sendAsString"); + ASSERT_TRUE(sendAsString); // Function found successfully. + + auto nameStr = std::string("person_Logan"); + auto expectReturnStr = ("sent_string_rvalue_" + nameStr); + + // Now invoke the rvalue-ref overload: `sendAsString(Person&&)`. + // To ensure this overload is selected, we must explicitly bind + // with `Person&&`. This is achieved through perfect forwarding, + // since overload resolution cannot deduce rvalue-ref automatically. + // + // The overload was registered, so the reflective call will succeed. + // If the argument type mismatches, `error::SignatureMismatch` will be returned. + auto [err, ret] = sendAsString->bind().call(Person(nameStr)); + + // Validate reflective call succeeded. + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + // Verify return type and extract result. + EXPECT_TRUE(ret.canViewAs()); + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + // Confirms the correct overload was invoked. + EXPECT_EQ(retStr, expectReturnStr); + } + + + TEST(MyReflectionTests, invoking_static_member_function_semantics) + { + // Retrieve the reflected class metadata. + std::optional classPerson = MyReflection().getRecord("Person"); + ASSERT_TRUE(classPerson); + + // Retrieve the static method from the class. + std::optional getDefaults = classPerson->getMethod("getDefaults"); + ASSERT_TRUE(getDefaults); + + auto expectReturnStr = std::string("Person_defaults_returned"); + + { + // Call the static member function directly. + // Semantics are the same as a free function: + // nothing to bind unless perfect-forwarding arguments are involved. + // Since it's static, no instance of the class is required. + auto [err, ret] = getDefaults->bind().call(); + + // Validate reflective call succeeded. + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + // Verify return type and extract result. + EXPECT_TRUE(ret.canViewAs()); + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + // Confirms the expected static function was invoked. + EXPECT_EQ(retStr, expectReturnStr); + } { + // Now create a `Person` object and reflect it into RTL. + rtl::RObject robj = rtl::reflect(Person("")); + + // Even if we bind a target object before calling the static function, + // it has no effect — the call remains valid and succeeds. + // This matches C++ native semantics: binding an instance is irrelevant + // for static member functions. + auto [err, ret] = getDefaults->bind(robj).call(); + + // Validate reflective call succeeded. + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + // Verify return type and extract result. + EXPECT_TRUE(ret.canViewAs()); + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + // Confirms the expected static function was invoked. + EXPECT_EQ(retStr, expectReturnStr); + } + } + + + TEST(MyReflectionTests, overload_resolution_semantics__constructor) + { + std::optional classPerson = MyReflection().getRecord("Person"); + ASSERT_TRUE(classPerson); + + std::string name = "Charlie"; + { + // Invokes the overloaded constructor that takes 'const std::string&'. + // It will not match the overload with 'std::string&', because arguments + // are forwarded as universal references (&&), which bind only to + // 'const std::string&'. This resolution is handled by the compiler, + // not by RTL. + auto [err, robj] = classPerson->create(name); + + EXPECT_TRUE(err == rtl::error::None); + ASSERT_TRUE(!robj.isEmpty()); + ASSERT_TRUE(robj.canViewAs()); + + auto view = robj.view(); + EXPECT_TRUE(view); + + const Person& person = view->get(); + EXPECT_EQ(name, person.name); + } + } + + + TEST(MyReflectionTests, overload_resolution_semantics__method) + { + // Tests runtime overload resolution between `std::string` (by value) + // and `std::string&` overloads of Person::setProfile. + std::optional classPerson = MyReflection().getRecord("Person"); + ASSERT_TRUE(classPerson); + + // Create a Person instance the regular way. + Person orgTim("Tim"); + + // Reflect into RObject. Internally this creates a copy of 'orgTim' on the stack. + rtl::RObject robjTim = rtl::reflect(orgTim); + + std::optional setProfile = classPerson->getMethod("setProfile"); + ASSERT_TRUE(setProfile); + + // NOTE for documentation: + // Calling with a constant-size array (like `"profStr"`) will not compile, + // because array-to-pointer decay is not supported here. + // Instead, use a `const char*` or `std::string`. + // auto [err, ret] = setProfile->bind(robjTim).call("profStr"); + + { + auto [err, ret] = setProfile->bind(robjTim).call(std::string("Tim's prof")); + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + // We know the return type is std::string. + EXPECT_TRUE(ret.canViewAs()); + + // Extract the std::string view from `ret`. + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + // Confirms that the overload taking `std::string` by value was invoked. + EXPECT_EQ(retStr, "called_by_val"); + } { + std::string profStr = "Tim's profile."; + auto [err, ret] = setProfile->bind(robjTim).call(profStr); + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + // Again, return type is std::string. + EXPECT_TRUE(ret.canViewAs()); + + // Extract the std::string view from `ret`. + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + // Even though we explicitly bound `std::string&`, + // runtime overload resolution still picked the by-value version. + EXPECT_EQ(retStr, "called_by_val"); + } + } + + + TEST(MyReflectionTests, perfect_forwarding_seamantics__rvalue_ref) + { + std::optional classPerson = MyReflection().getRecord("Person"); + ASSERT_TRUE(classPerson); + + // Create a Person instance the regular way. + Person orgTim("Tim"); + + // Reflect into RObject. Internally this creates a copy of 'orgTim' on the stack. + rtl::RObject robjTim = rtl::reflect(orgTim); + + std::optional setTitle = classPerson->getMethod("setTitle"); + ASSERT_TRUE(setTitle); + + { + // Attempt to call 'setTitle' with an rvalue string. + // This fails because reflection will first attempt to resolve the call + // against a by-value parameter (`std::string`) instead of the actual + // registered signature (`std::string&&`). + auto [err, ret] = setTitle->bind(robjTim).call(std::string("Mr.")); + EXPECT_TRUE(err == rtl::error::SignatureMismatch); + EXPECT_TRUE(ret.isEmpty()); + } { + // To invoke the method successfully, we must perfectly forward `std::string` as an rvalue-ref. + // This requires explicitly specifying `std::string&&` in the template parameter pack of `bind`. + // Note: passing a string literal works fine here, since it is implicitly convertible to `std::string`; + // wrapping with `std::string("Mr.")` is unnecessary. + auto [err, ret] = setTitle->bind(robjTim).call("Mr."); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(ret.isEmpty()); + + // We know the return type is std::string. + EXPECT_TRUE(ret.canViewAs()); + + // Extract the std::string view from `ret`. + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + // Confirms that the `setTitle(std::string&&)` overload was correctly invoked. + EXPECT_EQ(retStr, "called_by_ref_rvalue"); + } + } + + + TEST(MyReflectionTests, perfect_forwarding_semantics__overload_resolution) + { + std::optional classPerson = MyReflection().getRecord("Person"); + ASSERT_TRUE(classPerson); + + // Create a Person instance the regular way. + Person orgTim("Tim"); + + // Reflect into RObject. Internally this creates a copy of 'orgTim' on the stack. + rtl::RObject robjTim = rtl::reflect(orgTim); + + std::optional setOccupation = classPerson->getMethod("setOccupation"); + ASSERT_TRUE(setOccupation); + + { + // Attempt to call 'setOccupation' with an rvalue string. + // Expectation: should match the rvalue-ref overload (`std::string&&`). + // Actual: fails because reflection first attempts to match a by-value + // parameter (`std::string`) instead of the registered signature. + auto [err, ret] = setOccupation->bind(robjTim).call(std::string("Teacher")); + EXPECT_TRUE(err == rtl::error::SignatureMismatch); + EXPECT_TRUE(ret.isEmpty()); + } { + // Attempt to call 'setOccupation' with a const-lvalue string. + // Expectation: should match the const-lvalue-ref overload (`const std::string&`). + // Actual: fails for the same reason�reflection attempts by-value resolution first. + const std::string occupationStr = "Teacher"; + auto [err, ret] = setOccupation->bind(robjTim).call(std::string(occupationStr)); + EXPECT_TRUE(err == rtl::error::SignatureMismatch); + EXPECT_TRUE(ret.isEmpty()); + } { + // Correctly invoke the rvalue-ref overload by explicitly binding + // `std::string&&` in the template parameter pack and perfectly forwarding. + auto [err, ret] = setOccupation->bind(robjTim).call("Teacher"); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(ret.isEmpty()); + + // The return type is known to be std::string. + EXPECT_TRUE(ret.canViewAs()); + + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + // Confirms that the `setOccupation(std::string&&)` overload was correctly invoked. + EXPECT_EQ(retStr, "called_by_rvalue_ref"); + } { + // Correctly invoke the const-lvalue-ref overload by explicitly binding + // `const std::string&` in the template parameter pack and perfectly forwarding. + auto [err, ret] = setOccupation->bind(robjTim).call("Teacher"); + EXPECT_TRUE(err == rtl::error::None); + ASSERT_FALSE(ret.isEmpty()); + + // The return type is known to be std::string. + EXPECT_TRUE(ret.canViewAs()); + + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + // Confirms that the `setOccupation(const std::string&)` overload was correctly invoked. + EXPECT_EQ(retStr, "called_by_ref_lvalue"); + } + } + + + TEST(MyReflectionTests, const_method_semantics__on_true_const_target) + { + std::optional classPerson = MyReflection().getRecord("Person"); + ASSERT_TRUE(classPerson); + + std::optional getProfile = classPerson->getMethod("getProfile"); + ASSERT_TRUE(getProfile); + { + // Case 1: Reflecting a true-const Person. + const Person constSam = Person("Const-Sam"); + + // Reflect 'const Person' into RObject. + rtl::RObject robj = rtl::reflect(constSam); + + // RTL never performs an implicit const_cast on externally provided true-const objects. + // Since 'constSam' is genuinely const, RTL preserves that constness. + // This applies equally to any object returned by reflective calls. + EXPECT_FALSE(robj.isConstCastSafe()); + { + std::string expectReturnStr = "only_const_method_version_exists"; + + // For 'getProfile' only a const overload is registered. + // Since 'robj' reflects a const object, it naturally invokes the const overload. + auto [err, ret] = getProfile->bind(robj).call(); + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + // Validate return type and value. + EXPECT_TRUE(ret.canViewAs()); + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + EXPECT_EQ(retStr, expectReturnStr); + } { + // Attempt to bind a truly-const object to a non-const method. + // This forces RTL to try a const_cast on the reflected object. + // Since the object is genuinely const, the cast would be unsafe. + // However, the call fails earlier because no non-const overload is registered. + auto [err, ret] = getProfile->bind(rtl::constCast(robj)).call(); + // Expected: NonConstOverloadMissing. + EXPECT_TRUE(err == rtl::error::NonConstOverloadMissing); + EXPECT_TRUE(ret.isEmpty()); + } + } + } + + + TEST(MyReflectionTests, non_const_method_semantics__on_true_const_target) + { + std::optional classPerson = MyReflection().getRecord("Person"); + ASSERT_TRUE(classPerson); + + std::optional getName = classPerson->getMethod("getName"); + ASSERT_TRUE(getName); + { + // Case 1: Reflecting a true-const Person. + const Person constSam = Person("Const-Sam"); + + // Reflect 'const Person' into RObject. + rtl::RObject robj = rtl::reflect(constSam); + + EXPECT_FALSE(robj.isConstCastSafe()); + { + auto [err, ret] = getName->bind(robj).call(); + // 'robj' reflects a true-const Person, but 'getName' is non-const. + // RTL searches for a const overload, which is not present. + // Expected: ConstOverloadMissing. + EXPECT_TRUE(err == rtl::error::ConstOverloadMissing); + EXPECT_TRUE(ret.isEmpty()); + } { + // Explicitly attempt to bind the true-const object to a non-const method. + // Since the object is truly const, const_cast is unsafe. + auto [err, ret] = getName->bind(rtl::constCast(robj)).call(); + // Expected: IllegalConstCast. + EXPECT_TRUE(err == rtl::error::IllegalConstCast); + EXPECT_TRUE(ret.isEmpty()); + } + } + } + + + TEST(MyReflectionTests, const_method_semantics__on_logical_const_target) + { + std::optional classPerson = MyReflection().getRecord("Person"); + ASSERT_TRUE(classPerson); + + std::optional getProfile = classPerson->getMethod("getProfile"); + ASSERT_TRUE(getProfile); + + // Case 2: Reflecting a mutable Person. + Person mutableSam = Person("Dash"); + + // Reflect 'Person' into RObject (copy created on stack). + rtl::RObject robj = rtl::reflect(mutableSam); + + // RTL treats reflection-created objects as logically immutable by default. + // For such objects, const_cast is always safe since RTL controls their lifetime. + EXPECT_TRUE(robj.isConstCastSafe()); + { + std::string expectReturnStr = "only_const_method_version_exists"; + + auto [err, ret] = getProfile->bind(robj).call(); + // 'robj' is logically const. Since only a const overload is registered, + // RTL safely invokes the const version. + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + // Validate return type and value. + EXPECT_TRUE(ret.canViewAs()); + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + EXPECT_EQ(retStr, expectReturnStr); + } { + // Explicitly attempt to call a non-const method on a truly-const object. + // RTL would need a const_cast, which is safe here, but the call fails earlier. + // Since the non-const overload is not registered, RTL raises NonConstOverloadMissing. + auto [err, ret] = getProfile->bind(rtl::constCast(robj)).call(); + EXPECT_TRUE(err == rtl::error::NonConstOverloadMissing); + EXPECT_TRUE(ret.isEmpty()); + } + } + + + TEST(MyReflectionTests, non_const_method_semantics__on_logical_const_target) + { + std::optional classPerson = MyReflection().getRecord("Person"); + ASSERT_TRUE(classPerson); + + std::optional getName = classPerson->getMethod("getName"); + ASSERT_TRUE(getName); + + // Case 2: Reflecting a mutable Person. + Person mutableSam = Person("Mutable-Sam"); + + rtl::RObject robj = rtl::reflect(mutableSam); + EXPECT_TRUE(robj.isConstCastSafe()); + + std::string expectReturnStr = "called_non_const__Mutable-Sam"; + { + auto [err, ret] = getName->bind(robj).call(); + // 'robj' is logically const, but since only a non-const overload exists, + // RTL safely applies const_cast internally and invokes it. + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + // Validate return type and value. + EXPECT_TRUE(ret.canViewAs()); + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + EXPECT_EQ(retStr, expectReturnStr); + } { + // Explicit request for the non-const overload via rtl::constCast. + // Safe here, since the object is not truly const. + auto [err, ret] = getName->bind(rtl::constCast(robj)).call(); + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + EXPECT_TRUE(ret.canViewAs()); + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + EXPECT_EQ(retStr, expectReturnStr); + } + } + + + TEST(MyReflectionTests, const_based_overload_resolution_semantics__on_true_const_target) + { + std::optional classPerson = MyReflection().getRecord("Person"); + ASSERT_TRUE(classPerson); + + std::optional updateAddress = classPerson->getMethod("updateAddress"); + ASSERT_TRUE(updateAddress); + { + // Case 1: Reflecting a true-const Person. + const Person constSam = Person("Const-Sam"); + + rtl::RObject robj = rtl::reflect(constSam); + EXPECT_FALSE(robj.isConstCastSafe()); + { + std::string expectReturnStr = "called_const_overload"; + // Both const and non-const overloads are registered. + // Since 'robj' is true-const, RTL automatically invokes the const overload. + auto [err, ret] = updateAddress->bind(robj).call(); + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + EXPECT_TRUE(ret.canViewAs()); + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + EXPECT_EQ(retStr, expectReturnStr); + } { + // Explicitly attempt to call the non-const overload via constCast. + // Unsafe here, since the object is truly const. + auto [err, ret] = updateAddress->bind(rtl::constCast(robj)).call(); + // Expected: IllegalConstCast. + EXPECT_TRUE(err == rtl::error::IllegalConstCast); + EXPECT_TRUE(ret.isEmpty()); + } + } + } + + + TEST(MyReflectionTests, const_based_overload_resolution_semantics__on_logical_const_target) + { + std::optional classPerson = MyReflection().getRecord("Person"); + ASSERT_TRUE(classPerson); + + std::optional updateAddress = classPerson->getMethod("updateAddress"); + ASSERT_TRUE(updateAddress); + + // Case 2: Reflecting a mutable Person. + Person mutableSam = Person("Mutable-Sam"); + + rtl::RObject robj = rtl::reflect(mutableSam); + EXPECT_TRUE(robj.isConstCastSafe()); + { + std::string expectReturnStr = "called_const_overload"; + // Both const and non-const overloads are registered. + // Since 'robj' is logically const, RTL invokes the const overload. + auto [err, ret] = updateAddress->bind(robj).call(); + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + EXPECT_TRUE(ret.canViewAs()); + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + EXPECT_EQ(retStr, expectReturnStr); + } { + std::string expectReturnStr = "called_non_const_overload"; + // Explicit request for the non-const overload via rtl::constCast. + // Safe here, since the object is not truly const. + auto [err, ret] = updateAddress->bind(rtl::constCast(robj)).call(); + EXPECT_TRUE(err == rtl::error::None); + EXPECT_FALSE(ret.isEmpty()); + + EXPECT_TRUE(ret.canViewAs()); + std::optional> strView = ret.view(); + ASSERT_TRUE(strView); + + const std::string& retStr = strView->get(); + EXPECT_EQ(retStr, expectReturnStr); + } + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/RObjectTests/RObjectImplicitConversions.cpp b/RTLTestRunApp/src/RObjectTests/RObjectImplicitConversions.cpp new file mode 100644 index 00000000..ccfc6809 --- /dev/null +++ b/RTLTestRunApp/src/RObjectTests/RObjectImplicitConversions.cpp @@ -0,0 +1,56 @@ + +#include + +#include "ConversionUtils.h" + +namespace rtl_tests +{ + TEST(RObject_conversions, implicit_safe_and_unsafe) + { + using rtl::traits::is_safe_conversion_v; + + // === Self conversions (trivial) === + static_assert(is_safe_conversion_v); + static_assert(is_safe_conversion_v); + static_assert(is_safe_conversion_v); + + //static_assert(is_safe_conversion_v); // pointer -> void* + + // === Safe conversions (should all be true) === + static_assert(is_safe_conversion_v); // widening bool -> int + static_assert(is_safe_conversion_v); // widening bool -> double + + static_assert(is_safe_conversion_v); // widening int -> long + static_assert(is_safe_conversion_v); // widening int -> long long + static_assert(is_safe_conversion_v); // exact int -> double + static_assert(is_safe_conversion_v); // true on typical platforms + static_assert(is_safe_conversion_v); // true + static_assert(is_safe_conversion_v); + static_assert(is_safe_conversion_v); + + static_assert(is_safe_conversion_v); // widening char -> int + static_assert(is_safe_conversion_v); // widening short -> int + static_assert(is_safe_conversion_v); // widening float -> double + static_assert(is_safe_conversion_v); + static_assert(is_safe_conversion_v); + + // === Unsafe conversions (should all be false) === + static_assert(!is_safe_conversion_v); // narrowing + static_assert(!is_safe_conversion_v); + static_assert(!is_safe_conversion_v); // narrowing float precision + static_assert(!is_safe_conversion_v); // narrowing + truncation + static_assert(!is_safe_conversion_v); // narrowing + truncation + static_assert(!is_safe_conversion_v); // narrowing + static_assert(!is_safe_conversion_v); // narrowing + static_assert(!is_safe_conversion_v); // sign change + static_assert(!is_safe_conversion_v); // sign change + static_assert(!is_safe_conversion_v); // pointer -> pointer different type + static_assert(!is_safe_conversion_v); // dropping const + static_assert(!is_safe_conversion_v); // adding const (still not safe in our conservative def) + static_assert(!is_safe_conversion_v); // // Technically "safe" but yields large number (-1) + static_assert(!is_safe_conversion_v); // false + static_assert(!is_safe_conversion_v); // false + + EXPECT_TRUE(true); + } +} diff --git a/RTLTestRunApp/src/RObjectTests/RObjectReflecting_arrays.cpp b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_arrays.cpp new file mode 100644 index 00000000..1a1753c6 --- /dev/null +++ b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_arrays.cpp @@ -0,0 +1,74 @@ +/** + * @file RObjectReflectionTests.cpp + * @brief Unit tests for rtl::RObject reflection system, validating support for std::vector and trivial C-style arrays. + * + * This suite tests the behavior of `rtl::reflect` and `.view()`: + * - Ensures reflection works for lvalue, rvalue, and pointer forms. + * - Verifies zero-copy behavior for pointer-based inputs. + * - Confirms support for reflection of C-style arrays into std::array. + * + * Components tested: + * - `rtl::reflect` -> creates RObject from value or pointer + * - `RObject::view` -> provides typed, non-owning access to the internal value + * - `canViewAs` -> checks if a view of type T is supported + */ + +#include + +#include "RTLibInterface.h" + +using namespace rtl; + +namespace rtl_tests { + + // Test: Reflect lvalue std::vector + TEST(RObject_view_vector, init_with_stdVector_int_lvalue) + { + std::vector input = { 1, 2, 3, 4, 5 }; + RObject robj = rtl::reflect(input); // reflect by copy + + ASSERT_TRUE(robj.canViewAs>()); + + auto vec_view = robj.view>(); + ASSERT_TRUE(vec_view.has_value()); + + const std::vector& inputView = vec_view->get(); + ASSERT_EQ(inputView, input); + } + + + // Test: Reflect rvalue std::vector + TEST(RObject_view_vector, init_with_stdVector_int_rvalue) + { + RObject robj = rtl::reflect(std::vector({ 1, 2, 3, 4, 5 })); + + ASSERT_TRUE(robj.canViewAs>()); + + auto vec_view = robj.view>(); + ASSERT_TRUE(vec_view.has_value()); + + const std::vector& inputView = vec_view->get(); + ASSERT_EQ(inputView, std::vector({ 1, 2, 3, 4, 5 })); + } + + // Macro: Generate tests for trivial C-style arrays -> std::array + #define TEST_TRIVIAL_ARRAY_REFLECTION(TYPE, SIZE, ...) \ + TEST(RObject_array_reflection, reflect_##TYPE##_array_##SIZE) \ + { \ + TYPE data[SIZE] = { __VA_ARGS__ }; \ + RObject robj = rtl::reflect(data); \ + ASSERT_TRUE(robj.canViewAs>()); \ + auto view = robj.view>(); \ + ASSERT_TRUE(view.has_value()); \ + const std::vector& arr = view->get(); \ + for (size_t i = 0; i < arr.size(); ++i) \ + EXPECT_EQ(arr[i], data[i]); \ + } + + // Tests for all trivial types with various array sizes + TEST_TRIVIAL_ARRAY_REFLECTION(int, 3, 1, 2, 3) + TEST_TRIVIAL_ARRAY_REFLECTION(float, 4, 1.0f, 2.0f, 3.0f, 4.0f) + TEST_TRIVIAL_ARRAY_REFLECTION(double, 2, 3.14, 2.71) + TEST_TRIVIAL_ARRAY_REFLECTION(bool, 3, true, false, true) + +} // namespace rtl_tests diff --git a/RTLTestRunApp/src/RObjectTests/RObjectReflecting_bool.cpp b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_bool.cpp new file mode 100644 index 00000000..914b0dc7 --- /dev/null +++ b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_bool.cpp @@ -0,0 +1,168 @@ + +#include + +#include "RTLibInterface.h" + +using namespace rtl; + + +namespace rtl_tests +{ + TEST(RObject_bool_value, reflect_bool_view_as_bool) + { + // Reflect a bool value into RObject + RObject robj = rtl::reflect(true); + + // Check if RObject can be viewed as bool (true type or convertible) + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as bool + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the underlying value + const bool& cref = view->get(); + + // Confirm the value is equal to the original + ASSERT_EQ(cref, true); + } + + + TEST(RObject_bool_value, reflect_bool_view_as_int) + { + // Reflect a bool value (false) into RObject + RObject robj = rtl::reflect(false); + + // Check if RObject can be viewed as int (via conversion) + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as int + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted int value + const int& cref = view->get(); + + // Confirm the value matches expected result of bool(false) -> int(0) + ASSERT_EQ(cref, 0); + } + + + // Test reflecting a bool and viewing it as char + TEST(RObject_bool_value, reflect_bool_view_as_char) + { + // Reflect the value `true` into RObject + RObject robj = rtl::reflect(true); + + // Check if the RObject can reflect as `char` + ASSERT_TRUE(robj.canViewAs()); + + // Get the reflected value as `char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted char value + const char& cref = view->get(); + + // Verify the reflected `char` value is correct + ASSERT_EQ(cref, static_cast(true)); + } + + + // Test reflecting a bool and viewing it as signed char + TEST(RObject_bool_value, reflect_bool_view_as_signed_char) + { + // Reflect the value `false` into RObject + RObject robj = rtl::reflect(false); + + // Check if the value can be reflected as `signed char` + ASSERT_TRUE(robj.canViewAs()); + + // Get the reflected value as `signed char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted signed char value + const signed char& cref = view->get(); + + // Verify the converted value matches the original bool value + ASSERT_EQ(cref, static_cast(false)); + } + + + // Test reflecting a bool and viewing it as unsigned char + TEST(RObject_bool_value, reflect_bool_view_as_unsigned_char) + { + // Reflect the value `true` into RObject + RObject robj = rtl::reflect(true); + + // Check if RObject can reflect as `unsigned char` + ASSERT_TRUE(robj.canViewAs()); + + // Get the reflected value as `unsigned char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted unsigned char value + const unsigned char& cref = view->get(); + + // Confirm the converted value matches the original bool value + ASSERT_EQ(cref, static_cast(true)); + } + + + // Test reflecting a bool and viewing it as short + TEST(RObject_bool_value, reflect_bool_view_as_short) + { + // Reflect the value `false` into RObject + RObject robj = rtl::reflect(false); + + // Check if the value can be reflected as `short` + ASSERT_TRUE(robj.canViewAs()); + + // Get the reflected value as `short` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted short value + const short& cref = view->get(); + + // Confirm the converted value matches original bool value + ASSERT_EQ(cref, static_cast(false)); + } + + + // Test reflecting a bool and viewing it as unsigned short + TEST(RObject_bool_value, reflect_bool_view_as_unsigned_short) + { + // Reflect the value `true` into RObject + RObject robj = rtl::reflect(true); + + // Check if the value can be reflected as `unsigned short` + ASSERT_TRUE(robj.canViewAs()); + + // Get the reflected value as `unsigned short` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted unsigned short value + const unsigned short& cref = view->get(); + + // Confirm the converted value matches original bool value + ASSERT_EQ(cref, static_cast(true)); + } +} diff --git a/RTLTestRunApp/src/RObjectTests/RObjectReflecting_char.cpp b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_char.cpp new file mode 100644 index 00000000..5acfa96e --- /dev/null +++ b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_char.cpp @@ -0,0 +1,124 @@ + +#include + +#include "RTLibInterface.h" + +using namespace rtl; + + +namespace rtl_tests +{ + // Test reflecting a char and viewing it as signed char + TEST(RObject_char_value, reflect_char_view_as_signed_char) + { + // Reflect the value 'A' (ASCII 65) into RObject + RObject robj = rtl::reflect('A'); + + // Check if RObject can reflect as `signed char` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `signed char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted signed char value + const signed char& cref = view->get(); + + // Verify the conversion result + ASSERT_EQ(cref, static_cast('A')); + } + + + // Test reflecting a char and viewing it as unsigned char + TEST(RObject_char_value, reflect_char_view_as_unsigned_char) + { + // Reflect the value 'A' (ASCII 65) into RObject + RObject robj = rtl::reflect('A'); + + // Check if RObject can reflect as `unsigned char` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `unsigned char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted unsigned char value + const unsigned char& cref = view->get(); + + // Verify the conversion result + ASSERT_EQ(cref, static_cast('A')); + } + + + // Test reflecting a char and viewing it as short + TEST(RObject_char_value, reflect_char_view_as_short) + { + // Reflect the value 'A' (ASCII 65) into RObject + RObject robj = rtl::reflect('A'); + + // Check if RObject can reflect as `short` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `short` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted short value + const short& cref = view->get(); + + // Verify the conversion result + ASSERT_EQ(cref, static_cast('A')); + } + + + // Test reflecting a char and viewing it as unsigned short + TEST(RObject_char_value, reflect_char_view_as_unsigned_short) + { + // Reflect the value 'A' (ASCII 65) into RObject + RObject robj = rtl::reflect('A'); + + // Check if RObject can reflect as `unsigned short` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `unsigned short` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted unsigned short value + const unsigned short& cref = view->get(); + + // Verify the conversion result + ASSERT_EQ(cref, static_cast('A')); + } + + + // Test reflecting a char and viewing it as int + TEST(RObject_char_value, reflect_char_view_as_int) + { + // Reflect the value 'A' (ASCII 65) into RObject + RObject robj = rtl::reflect('A'); + + // Check if RObject can reflect as `int` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `int` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted int value + const int& cref = view->get(); + + // Verify the conversion result + ASSERT_EQ(cref, static_cast('A')); + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/RObjectTests/RObjectReflecting_int.cpp b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_int.cpp new file mode 100644 index 00000000..57b8cd6c --- /dev/null +++ b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_int.cpp @@ -0,0 +1,805 @@ + +#include + +#include "RTLibInterface.h" + +using namespace rtl; + +namespace rtl_tests +{ + // Test reflecting an int and viewing it as bool + TEST(RObject_int_rvalue, reflect_int_view_as_bool) + { + // Reflect an int value (e.g., 5) into RObject + RObject robj = rtl::reflect(5); + + // Check if RObject can reflect as `bool` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `bool` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted bool value + const bool& cref = view->get(); + + // Verify the conversion result (non-zero -> true) + ASSERT_EQ(cref, true); + } + + + // Test reflecting an int and viewing it as char + TEST(RObject_int_rvalue, reflect_int_view_as_char) + { + // Reflect an int value (e.g., 65) into RObject + RObject robj = rtl::reflect(65); + + // Check if RObject can reflect as `char` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted char value + const char& cref = view->get(); + + // Verify the conversion result (65 -> 'A') + ASSERT_EQ(cref, static_cast(65)); + } + + + // Test reflecting an int and viewing it as signed char + TEST(RObject_int_rvalue, reflect_int_view_as_signed_char) + { + // Reflect an int value (e.g., 97) into RObject + RObject robj = rtl::reflect(97); + + // Check if RObject can reflect as `signed char` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `signed char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted signed char value + const signed char& cref = view->get(); + + // Verify the conversion result (97 -> 'a') + ASSERT_EQ(cref, static_cast(97)); + } + + + // Test reflecting an int and viewing it as unsigned char + TEST(RObject_int_rvalue, reflect_int_view_as_unsigned_char) + { + // Reflect an int value (e.g., 255) into RObject + RObject robj = rtl::reflect(255); + + // Check if RObject can reflect as `unsigned char` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `unsigned char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted unsigned char value + const unsigned char& cref = view->get(); + + // Verify the conversion result (255 -> '\xff') + ASSERT_EQ(cref, static_cast(255)); + } + + + // Test reflecting an int and viewing it as short + TEST(RObject_int_rvalue, reflect_int_view_as_short) + { + // Reflect an int value (e.g., 32767) into RObject + RObject robj = rtl::reflect(32767); + + // Check if RObject can reflect as `short` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `short` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted short value + const short& cref = view->get(); + + // Verify the conversion result + ASSERT_EQ(cref, static_cast(32767)); + } + + + // Test reflecting an int and viewing it as unsigned short + TEST(RObject_int_rvalue, reflect_int_view_as_unsigned_short) + { + // Reflect an int value (e.g., 65535) into RObject + RObject robj = rtl::reflect(65535); + + // Check if RObject can reflect as `unsigned short` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `unsigned short` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted unsigned short value + const unsigned short& cref = view->get(); + + // Verify the conversion result + ASSERT_EQ(cref, static_cast(65535)); + } +} + + +namespace rtl_tests +{ + // Test reflecting an int and viewing it as bool + TEST(RObject_int_lvalue, reflect_int_ptr_view_as_int_ptr) + { + int value = 5; // Example int value + + // Reflect an int value pointer into RObject + RObject robj = rtl::reflect(&value); + + // Check if RObject can reflect as `const int *` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `const int *` + auto view = robj.view(); + + // Ensure the view is valid + ASSERT_TRUE(view.has_value()); + + // Access the pointer returned by the view + const int& cref = view->get(); + + // Verify the addresses are same, no copy made. + ASSERT_EQ(&cref, &value); + } + + + // Test reflecting an int and viewing it as bool + TEST(RObject_int_lvalue, reflect_int_ptr_view_as_int_value) + { + int value = 5; // Example int value + + // Reflect an int value pointer into RObject + RObject robj = rtl::reflect(&value); + + // Check if RObject can reflect as `int` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `int` + auto view = robj.view(); + + // Ensure the view is valid + ASSERT_TRUE(view.has_value()); + + // Access the pointer returned by the view + int cref = view->get(); + + // Verify the addresses are same, no copy made. + ASSERT_EQ(cref, value); + } + + + // Test reflecting an int and viewing it as bool + TEST(RObject_int_lvalue, reflect_int_view_as_bool) + { + int value = 5; // Example int value + + // Reflect an int value (e.g., 5) into RObject + RObject robj = rtl::reflect(value); + + // Check if RObject can reflect as `bool` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `bool` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted bool value + const bool& cref = view->get(); + + // Verify the conversion result (non-zero -> true) + ASSERT_EQ(cref, true); + } + + + // Test reflecting an int and viewing it as char + TEST(RObject_int_lvalue, reflect_int_view_as_char) + { + int value = 65; // Example int value + + // Reflect an int value (e.g., 65) into RObject + RObject robj = rtl::reflect(value); + + // Check if RObject can reflect as `char` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted char value + const char& cref = view->get(); + + // Verify the conversion result (65 -> 'A') + ASSERT_EQ(cref, static_cast(value)); + } + + + // Test reflecting an int and viewing it as signed char + TEST(RObject_int_lvalue, reflect_int_view_as_signed_char) + { + int value = 97; // Example int value + + // Reflect an int value (e.g., 97) into RObject + RObject robj = rtl::reflect(value); + + // Check if RObject can reflect as `signed char` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `signed char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted signed char value + const signed char& cref = view->get(); + + // Verify the conversion result (97 -> 'a') + ASSERT_EQ(cref, static_cast(value)); + } + + + // Test reflecting an int and viewing it as unsigned char + TEST(RObject_int_lvalue, reflect_int_view_as_unsigned_char) + { + int value = 255; // Example int value + + // Reflect an int value (e.g., 255) into RObject + RObject robj = rtl::reflect(value); + + // Check if RObject can reflect as `unsigned char` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `unsigned char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted unsigned char value + const unsigned char& cref = view->get(); + + // Verify the conversion result (255 -> '\xff') + ASSERT_EQ(cref, static_cast(value)); + } + + + // Test reflecting an int and viewing it as short + TEST(RObject_int_lvalue, reflect_int_view_as_short) + { + int value = 32767; // Example int value + + // Reflect an int value (e.g., 32767) into RObject + RObject robj = rtl::reflect(value); + + // Check if RObject can reflect as `short` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `short` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted short value + const short& cref = view->get(); + + // Verify the conversion result + ASSERT_EQ(cref, static_cast(value)); + } + + + // Test reflecting an int and viewing it as unsigned short + TEST(RObject_int_lvalue, reflect_int_view_as_unsigned_short) + { + int value = 65535; // Example int value + + // Reflect an int value (e.g., 65535) into RObject + RObject robj = rtl::reflect(value); + + // Check if RObject can reflect as `unsigned short` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `unsigned short` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted unsigned short value + const unsigned short& cref = view->get(); + + // Verify the conversion result + ASSERT_EQ(cref, static_cast(value)); + } +} + + + + +namespace rtl_tests +{ + // Test reflecting an int* and viewing it as bool + TEST(RObject_int_pointer_lvalue, reflect_int_view_as_bool_true) + { + int *ptr = new int(5); + + // Reflect an int value (e.g., 5) into RObject + RObject robj = rtl::reflect(ptr); + + // Check if RObject can reflect as `bool` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `bool` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted bool value + const bool& cref = view->get(); + + // Verify the conversion result (non-zero -> true) + ASSERT_EQ(cref, true); + + delete ptr; // Clean up the dynamically allocated memory + } + + + // Test reflecting an int* and viewing it as bool + TEST(RObject_int_pointer_lvalue, reflect_int_view_as_bool_false) + { + int* ptr = new int(0); + + // Reflect an int value (e.g., 5) into RObject + RObject robj = rtl::reflect(ptr); + + // Check if RObject can reflect as `bool` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `bool` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted bool value + const bool& cref = view->get(); + + // Verify the conversion result (non-zero -> true) + ASSERT_EQ(cref, false); + } + + + // Test reflecting an int* and viewing it as char + TEST(RObject_int_pointer_lvalue, reflect_int_view_as_char) + { + int* ptr = new int(65); + + // Reflect an int value (e.g., 65) into RObject + RObject robj = rtl::reflect(ptr); + + // Check if RObject can reflect as `char` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted char value + const char& cref = view->get(); + + // Verify the conversion result (65 -> 'A') + ASSERT_EQ(cref, static_cast(65)); + + delete ptr; // Clean up the dynamically allocated memory + } + + + // Test reflecting an int* and viewing it as signed char + TEST(RObject_int_pointer_lvalue, reflect_int_view_as_signed_char) + { + int* ptr = new int(97); + + // Reflect an int value (e.g., 97) into RObject + RObject robj = rtl::reflect(ptr); + + // Check if RObject can reflect as `signed char` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `signed char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted signed char value + const signed char& cref = view->get(); + + // Verify the conversion result (97 -> 'a') + ASSERT_EQ(cref, static_cast(97)); + + delete ptr; // Clean up the dynamically allocated memory + } + + + // Test reflecting an int and viewing it as unsigned char + TEST(RObject_int_pointer_lvalue, reflect_int_view_as_unsigned_char) + { + int* ptr = new int(255); + + // Reflect an int value (e.g., 255) into RObject + RObject robj = rtl::reflect(ptr); + + // Check if RObject can reflect as `unsigned char` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `unsigned char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted unsigned char value + const unsigned char& cref = view->get(); + + // Verify the conversion result (255 -> '\xff') + ASSERT_EQ(cref, static_cast(255)); + + delete ptr; // Clean up the dynamically allocated memory + } + + + // Test reflecting an int and viewing it as short + TEST(RObject_int_pointer_lvalue, reflect_int_view_as_short) + { + int* ptr = new int(32767); + + // Reflect an int value (e.g., 32767) into RObject + RObject robj = rtl::reflect(ptr); + + // Check if RObject can reflect as `short` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `short` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted short value + const short& cref = view->get(); + + // Verify the conversion result + ASSERT_EQ(cref, static_cast(32767)); + + delete ptr; // Clean up the dynamically allocated memory + } + + + // Test reflecting an int and viewing it as unsigned short + TEST(RObject_int_pointer_lvalue, reflect_int_view_as_unsigned_short) + { + int* ptr = new int(65535); + + // Reflect an int value (e.g., 65535) into RObject + RObject robj = rtl::reflect(ptr); + + // Check if RObject can reflect as `unsigned short` + ASSERT_TRUE(robj.canViewAs()); + + // Get a view of the value as `unsigned short` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted unsigned short value + const unsigned short& cref = view->get(); + + // Verify the conversion result + ASSERT_EQ(cref, static_cast(65535)); + + delete ptr; // Clean up the dynamically allocated memory + } +} + + +namespace rtl_tests +{ + // Test reflecting an int* and viewing it as bool + TEST(RObject_int_pointer_rvalue, reflect_int_view_as_bool_true) + { + /* Reflect an int value(e.g., 5) into RObject + * Intentionally relinquishing ownership of dynamically allocated memory + * to test RObject creation with an rvalue pointer. + */ RObject robj = rtl::reflect(new int(5)); + + // Check if RObject can reflect as `bool` + ASSERT_TRUE(robj.canViewAs()); + { + // Get a view of the value as `bool` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted bool value + const bool& cref = view->get(); + + // Verify the conversion result (non-zero -> true) + ASSERT_EQ(cref, true); + } { + ASSERT_TRUE(robj.canViewAs()); + + auto view = robj.view(); + + ASSERT_TRUE(view.has_value()); + + // rtl::view<> holds ref to the entity in RObject; + // delete the dynamically allocated memory (new int) + const int& cref = view->get(); + delete& cref; + } + } + + + // Test reflecting an int* and viewing it as bool + TEST(RObject_int_pointer_rvalue, reflect_int_view_as_bool_false) + { + /* Reflect an int value (e.g., 0) into RObject + * Intentionally relinquishing ownership of dynamically allocated memory + * to test RObject creation with an rvalue pointer. + */ RObject robj = rtl::reflect(new int(0)); + + // Check if RObject can reflect as `bool` + ASSERT_TRUE(robj.canViewAs()); + { + // Get a view of the value as `bool` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted bool value + const bool& cref = view->get(); + + // Verify the conversion result (non-zero -> true) + ASSERT_EQ(cref, false); + } { + ASSERT_TRUE(robj.canViewAs()); + + auto view = robj.view(); + + ASSERT_TRUE(view.has_value()); + + // rtl::view<> holds ref to the entity in RObject; + // delete the dynamically allocated memory (new int) + const int& cref = view->get(); + delete& cref; + } + + //Caution: The dynamically allocated memory (new int) is not deleted here. + } + + + // Test reflecting an int* and viewing it as char + TEST(RObject_int_pointer_rvalue, reflect_int_view_as_char) + { + /* Reflect an int value(e.g., 65) into RObject + * Intentionally relinquishing ownership of dynamically allocated memory + * to test RObject creation with an rvalue pointer. + */ RObject robj = rtl::reflect(new int(65)); + + // Check if RObject can reflect as `char` + ASSERT_TRUE(robj.canViewAs()); + { + // Get a view of the value as `char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted char value + const char& cref = view->get(); + + // Verify the conversion result (65 -> 'A') + ASSERT_EQ(cref, static_cast(65)); + } { + ASSERT_TRUE(robj.canViewAs()); + + auto view = robj.view(); + + ASSERT_TRUE(view.has_value()); + + // rtl::view<> holds ref to the entity in RObject; + // delete the dynamically allocated memory (new int) + const int& cref = view->get(); + delete& cref; + } + } + + + // Test reflecting an int* and viewing it as signed char + TEST(RObject_int_pointer_rvalue, reflect_int_view_as_signed_char) + { + /* Reflect an int value(e.g., 97) into RObject + * Intentionally relinquishing ownership of dynamically allocated memory + * to test RObject creation with an rvalue pointer. + */ RObject robj = rtl::reflect(new int(97)); + + // Check if RObject can reflect as `signed char` + ASSERT_TRUE(robj.canViewAs()); + { + // Get a view of the value as `signed char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted signed char value + const signed char& cref = view->get(); + + // Verify the conversion result (97 -> 'a') + ASSERT_EQ(cref, static_cast(97)); + } { + ASSERT_TRUE(robj.canViewAs()); + + auto view = robj.view(); + + ASSERT_TRUE(view.has_value()); + + // rtl::view<> holds ref to the entity in RObject; + // delete the dynamically allocated memory (new int) + const int& cref = view->get(); + delete& cref; + } + } + + + // Test reflecting an int and viewing it as unsigned char + TEST(RObject_int_pointer_rvalue, reflect_int_view_as_unsigned_char) + { + /* Reflect an int value(e.g., 255) into RObject + * Intentionally relinquishing ownership of dynamically allocated memory + * to test RObject creation with an rvalue pointer. + */ RObject robj = rtl::reflect(new int(255)); + + // Check if RObject can reflect as `unsigned char` + ASSERT_TRUE(robj.canViewAs()); + { + // Get a view of the value as `unsigned char` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted unsigned char value + const unsigned char& cref = view->get(); + + // Verify the conversion result (255 -> '\xff') + ASSERT_EQ(cref, static_cast(255)); + } { + ASSERT_TRUE(robj.canViewAs()); + + auto view = robj.view(); + + ASSERT_TRUE(view.has_value()); + + // rtl::view<> holds ref to the entity in RObject; + // delete the dynamically allocated memory (new int) + const int& cref = view->get(); + delete& cref; + } + } + + + // Test reflecting an int and viewing it as short + TEST(RObject_int_pointer_rvalue, reflect_int_view_as_short) + { + /* Reflect an int value(e.g., 32767) into RObject + * Intentionally relinquishing ownership of dynamically allocated memory + * to test RObject creation with an rvalue pointer. + */ RObject robj = rtl::reflect(new int(32767)); + + // Check if RObject can reflect as `short` + ASSERT_TRUE(robj.canViewAs()); + { + // Get a view of the value as `short` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted short value + const short& cref = view->get(); + + // Verify the conversion result + ASSERT_EQ(cref, static_cast(32767)); + } { + ASSERT_TRUE(robj.canViewAs()); + + auto view = robj.view(); + + ASSERT_TRUE(view.has_value()); + + // rtl::view<> holds ref to the entity in RObject; + // delete the dynamically allocated memory (new int) + const int& cref = view->get(); + delete& cref; + } + } + + + // Test reflecting an int and viewing it as unsigned short + TEST(RObject_int_pointer_rvalue, reflect_int_view_as_unsigned_short) + { + /* Reflect an int value(e.g., 65535) into RObject + * Intentionally relinquishing ownership of dynamically allocated memory + * to test RObject creation with an rvalue pointer. + */ RObject robj = rtl::reflect(new int(65535)); + + // Check if RObject can reflect as `unsigned short` + ASSERT_TRUE(robj.canViewAs()); + { + // Get a view of the value as `unsigned short` + auto view = robj.view(); + + // Ensure the view is valid (conversion succeeded) + ASSERT_TRUE(view.has_value()); + + // Access the converted unsigned short value + const unsigned short& cref = view->get(); + + // Verify the conversion result + ASSERT_EQ(cref, static_cast(65535)); + } { + ASSERT_TRUE(robj.canViewAs()); + + auto view = robj.view(); + + ASSERT_TRUE(view.has_value()); + + // rtl::view<> holds ref to the entity in RObject; + // delete the dynamically allocated memory (new int) + const int& cref = view->get(); + delete& cref; + } + } +} diff --git a/RTLTestRunApp/src/RObjectTests/RObjectReflecting_stdSharedPtr.cpp b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_stdSharedPtr.cpp new file mode 100644 index 00000000..30d463fa --- /dev/null +++ b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_stdSharedPtr.cpp @@ -0,0 +1,669 @@ + +#include +#include + +#include "Node.h" +#include "RTLibInterface.h" + +using namespace test_utils; +using namespace rtl; + +namespace { + + // Cloning is only available for types explicitly registered by the user. + // This is because cloning requires a lambda to be stored in a static table. + // Types reflected via rtl::reflect or obtained as the return value of a reflective call + // cannot be cloned unless they are explicitly registered. + + static rtl::CxxMirror cxx_mirror() + { + static rtl::CxxMirror m = rtl::CxxMirror({ + rtl::type().record("int").build(), + rtl::type().record("Node").build() + }); + return m; + } +} + +namespace rtl::unit_test +{ + TEST(RObject_reflecting_shared_ptr, sharing_semantics__pod) + { + constexpr const int NUM = -20438; + RObject robj = reflect(std::make_shared(NUM)); + ASSERT_FALSE(robj.isEmpty()); + + // Verify reflection at wrapper level. + // Ensure RObject recognizes it can be viewed as a shared_ptr. + EXPECT_TRUE(robj.canViewAs>()); + { + // Obtain a view of the shared_ptr. + auto view = robj.view>(); + ASSERT_TRUE(view.has_value()); + + { + // Accessing via 'const ref' does not increase reference count. + const std::shared_ptr& sptrVal = view->get(); + EXPECT_EQ(*sptrVal, NUM); + EXPECT_TRUE(sptrVal.use_count() == 1); + } { + // Copying the shared_ptr makes a shallow copy (ref-counted). + std::shared_ptr sptrVal = view->get(); + EXPECT_EQ(*sptrVal, NUM); + EXPECT_TRUE(sptrVal.use_count() == 2); + } + // Original view is still valid, back to count 1 after local copy goes out of scope. + const std::shared_ptr& sptr = view->get(); + EXPECT_TRUE(sptr.use_count() == 1); + } + + // Final state check. + // At the end, ownership should return to robj alone. + auto view = robj.view>(); + EXPECT_TRUE(view); + + const std::shared_ptr& sptr = view->get(); + EXPECT_TRUE(sptr.use_count() == 1); + } + + + TEST(RObject_reflecting_shared_ptr, sharing_semantics__Node) + { + ASSERT_TRUE(Node::instanceCount() == 0); + ASSERT_TRUE(Node::assertResourcesReleased()); + { + constexpr const int NUM = -45109; + RObject robj = reflect(std::make_shared(NUM)); + ASSERT_FALSE(robj.isEmpty()); + ASSERT_TRUE(Node::instanceCount() == 1); + + // Verify reflection at wrapper level. + // Ensure RObject recognizes it can be viewed as a shared_ptr. + EXPECT_TRUE(robj.canViewAs>()); + { + // Obtain a view of the shared_ptr. + auto view = robj.view>(); + ASSERT_TRUE(view.has_value()); + + { + // Accessing via 'const ref' does not increase reference count. + const std::shared_ptr& nptr = view->get(); + EXPECT_EQ(nptr->data(), NUM); + EXPECT_TRUE(nptr.use_count() == 1); + ASSERT_TRUE(Node::instanceCount() == 1); + } { + // Copying the shared_ptr makes a shallow copy (ref-counted). + std::shared_ptr nptr = view->get(); + EXPECT_EQ(nptr->data(), NUM); + EXPECT_TRUE(nptr.use_count() == 2); + ASSERT_TRUE(Node::instanceCount() == 1); + } + // Original view is still valid, back to count 1 after local copy goes out of scope. + EXPECT_TRUE(view->get().use_count() == 1); + } + // Final state check. + // At the end, ownership should return to robj alone. + auto view = robj.view>(); + EXPECT_TRUE(view); + + const std::shared_ptr& node = view->get(); + EXPECT_TRUE(node.use_count() == 1); + } + ASSERT_TRUE(Node::instanceCount() == 0); + ASSERT_TRUE(Node::assertResourcesReleased()); + } + + + TEST(RObject_reflecting_shared_ptr, cloning_semantics__pod_stack) + { + constexpr const int NUM = -20438; + RObject robj = reflect(std::make_shared(NUM)); + + error reterr = cxx_mirror().setupCloning(robj); + ASSERT_TRUE(reterr == error::None); + + ASSERT_FALSE(robj.isEmpty()); + + // --- Step 1: Clone by default (entity::Auto semantics) --- + { + // Default cloning shallow-copies the wrapper. + auto [err, robj0] = robj.clone(); + EXPECT_TRUE(err == error::None); + + // View clone as 'shared_ptr'. + EXPECT_TRUE(robj0.canViewAs>()); + + auto ptrView = robj0.view>(); + ASSERT_TRUE(ptrView); + + const std::shared_ptr& sptr = ptrView->get(); + EXPECT_EQ(*sptr, NUM); + + // View as the underlying int. + EXPECT_TRUE(robj0.canViewAs()); + + auto view = robj0.view(); + EXPECT_TRUE(view); + EXPECT_EQ(view->get(), NUM); + } + + // --- Step 2: Clone by 'Value' (entity::Value semantics) --- + { + // Copies the underlying value, *not* the wrapper. + auto [err, robj0] = robj.clone(); + EXPECT_TRUE(err == error::None); + + // Cannot view as shared_ptr, because we cloned the contained value. + EXPECT_FALSE(robj0.canViewAs>()); + + // Instead, can view as the underlying int. + EXPECT_TRUE(robj0.canViewAs()); + + auto view = robj0.view(); + EXPECT_TRUE(view); + EXPECT_EQ(view->get(), NUM); + } + + // --- Step 3: Clone with explicit wrapper semantics --- + { + // Explicitly request a clone at the wrapper level (entity::Wrapper). + // This performs a shallow copy of the shared_ptr, incrementing ref count. + auto [err, robj0] = robj.clone(); + EXPECT_TRUE(err == error::None); + + // Now the clone can also be viewed as shared_ptr. + EXPECT_TRUE(robj0.canViewAs>()); + + auto ptrView = robj0.view>(); + ASSERT_TRUE(ptrView); + + const std::shared_ptr& nptr = ptrView->get(); + EXPECT_EQ(*nptr, NUM); + + auto view = robj0.view>(); + ASSERT_TRUE(view.has_value()); + + { + // Access as const ref: no copy, ref count remains shared between robj & robj0. + const std::shared_ptr& sptrVal = view->get(); + EXPECT_EQ(*sptrVal, NUM); + EXPECT_TRUE(sptrVal.use_count() == 2); // shared by robj + robj0 + } { + // Explicit copy makes another shallow copy of the shared_ptr. + std::shared_ptr sptrVal = view->get(); + EXPECT_EQ(*sptrVal, NUM); + EXPECT_TRUE(sptrVal.use_count() == 3); // shared by robj + robj0 + sptrVal + } + // After local copy is gone, back to 2 owners (robj + robj0). + EXPECT_TRUE(view->get().use_count() == 2); + } + + // --- Step 4: Final state check --- + // At the end, ownership should return to robj alone. + auto view = robj.view>(); + EXPECT_TRUE(view); + + const std::shared_ptr& sptr = view->get(); + ASSERT_TRUE(sptr.use_count() == 1); + } + + + TEST(RObject_reflecting_shared_ptr, cloning_semantics__pod_heap) + { + constexpr const int NUM = -291823; + RObject robj = reflect(std::make_shared(NUM)); + ASSERT_FALSE(robj.isEmpty()); + + error reterr = cxx_mirror().setupCloning(robj); + ASSERT_TRUE(reterr == error::None); + + // --- Step 1: Clone by default (entity::Auto semantics) --- + { + // Default cloning shallow-copies the wrapper. + auto [err, robj0] = robj.clone(); + EXPECT_TRUE(err == error::StlWrapperHeapAllocForbidden); + ASSERT_TRUE(robj0.isEmpty()); + } + + // --- Step 2: Clone by 'Value' (entity::Value semantics) --- + { + // Copies the underlying value, *not* the wrapper. + auto [err, robj0] = robj.clone(); + EXPECT_TRUE(err == error::None); + + // Cannot view as shared_ptr, because we cloned the contained value. + EXPECT_FALSE(robj0.canViewAs>()); + + // Instead, can view as the underlying int. + EXPECT_TRUE(robj0.canViewAs()); + + auto view = robj0.view(); + EXPECT_TRUE(view); + EXPECT_EQ(view->get(), NUM); + } + + // --- Step 3: Clone with explicit wrapper semantics --- + { + // Explicitly request a clone at the wrapper level (entity::Wrapper). + // This performs a shallow copy of the shared_ptr, incrementing ref count. + auto [err, robj0] = robj.clone(); + EXPECT_TRUE(err == error::StlWrapperHeapAllocForbidden); + ASSERT_TRUE(robj0.isEmpty()); + } + + // --- Step 4: Final state check --- + // At the end, ownership should return to robj alone. + auto view = robj.view>(); + EXPECT_TRUE(view); + + const std::shared_ptr& sptr = view->get(); + ASSERT_TRUE(sptr.use_count() == 1); + } + + + TEST(RObject_reflecting_shared_ptr, cloning_semantics__Node_on_stack) + { + ASSERT_TRUE(Node::instanceCount() == 0); + ASSERT_TRUE(Node::assertResourcesReleased()); + { + constexpr const int NUM = -45109; + RObject robj = reflect(std::make_shared(NUM)); + ASSERT_FALSE(robj.isEmpty()); + ASSERT_TRUE(Node::instanceCount() == 1); + + error reterr = cxx_mirror().setupCloning(robj); + ASSERT_TRUE(reterr == error::None); + + // --- Step 2: Clone by default (entity::Auto semantics) --- + { + // Default cloning shallow-copies the wrapper. + auto [err, robj0] = robj.clone(); + EXPECT_TRUE(err == error::None); + ASSERT_TRUE(Node::instanceCount() == 1); + + // View clone as 'shared_ptr'. + EXPECT_TRUE(robj0.canViewAs>()); + + auto ptrView = robj0.view>(); + ASSERT_TRUE(ptrView); + + const auto& nptr = ptrView->get(); + EXPECT_EQ(nptr->data(), NUM); + + // View as the underlying Node. + EXPECT_TRUE(robj0.canViewAs()); + auto node = robj0.view(); + EXPECT_EQ(node->get().data(), NUM); + } + + // --- Step 3: Clone by 'Value' (entity::Value semantics) --- + { + // Copies the underlying value, *not* the wrapper. + auto [err, robj0] = robj.clone(); + EXPECT_TRUE(err == error::TypeNotCopyConstructible); + ASSERT_TRUE(Node::instanceCount() == 1); + ASSERT_TRUE(robj0.isEmpty()); + } + + // --- Step 4: Clone with explicit wrapper semantics --- + { + // Explicitly request a clone at the wrapper level (entity::Wrapper). + // This performs a shallow copy of the shared_ptr, incrementing ref count. + auto [err, robj0] = robj.clone(); + EXPECT_TRUE(err == error::None); + ASSERT_TRUE(Node::instanceCount() == 1); + + // Now the clone can also be viewed as shared_ptr. + EXPECT_TRUE(robj0.canViewAs>()); + + auto view = robj0.view>(); + ASSERT_TRUE(view.has_value()); + { + // Access as const ref: no copy, ref count remains shared between robj & robj0. + const std::shared_ptr& nptr = view->get(); + EXPECT_EQ(nptr->data(), NUM); + EXPECT_TRUE(nptr.use_count() == 2); // shared by robj + robj0 + } { + // Explicit copy makes another shallow copy of the shared_ptr. + std::shared_ptr nptr = view->get(); + EXPECT_EQ(nptr->data(), NUM); + EXPECT_TRUE(nptr.use_count() == 3); // shared by robj + robj0 + sptrVal + } + // After local copy is gone, back to 2 owners (robj + robj0). + EXPECT_TRUE(view->get().use_count() == 2); + } + + // --- Step 4: Final state check --- + // At the end, ownership should return to robj alone. + auto view = robj.view>(); + EXPECT_TRUE(view); + + const std::shared_ptr& node = view->get(); + ASSERT_TRUE(node.use_count() == 1); + } + ASSERT_TRUE(Node::instanceCount() == 0); + ASSERT_TRUE(Node::assertResourcesReleased()); + } + + + TEST(RObject_reflecting_shared_ptr, cloning_semantics__Node_on_heap) + { + ASSERT_TRUE(Node::instanceCount() == 0); + ASSERT_TRUE(Node::assertResourcesReleased()); + { + constexpr const int NUM = 241054; + RObject robj = reflect(std::make_shared(NUM)); + + error reterr = cxx_mirror().setupCloning(robj); + ASSERT_TRUE(reterr == error::None); + + ASSERT_FALSE(robj.isEmpty()); + ASSERT_TRUE(Node::instanceCount() == 1); + + // --- Step 2: Clone by default (entity::Auto semantics) --- + { + // Default cloning shallow-copies the wrapper. + auto [err, robj0] = robj.clone(); + EXPECT_TRUE(err == error::StlWrapperHeapAllocForbidden); + ASSERT_TRUE(robj0.isEmpty()); + ASSERT_TRUE(Node::instanceCount() == 1); + } + + // --- Step 3: Clone by 'Value' (entity::Value semantics) --- + { + // Copies the underlying value, *not* the wrapper. + auto [err, robj0] = robj.clone(); + EXPECT_TRUE(err == error::TypeNotCopyConstructible); + ASSERT_TRUE(Node::instanceCount() == 1); + ASSERT_TRUE(robj0.isEmpty()); + } + + // --- Step 4: Clone with explicit wrapper semantics --- + { + // Explicitly request a clone at the wrapper level (entity::Wrapper). + // This performs a shallow copy of the shared_ptr, incrementing ref count. + auto [err, robj0] = robj.clone(); + EXPECT_TRUE(err == error::StlWrapperHeapAllocForbidden); + ASSERT_TRUE(robj0.isEmpty()); + ASSERT_TRUE(Node::instanceCount() == 1); + } + + // --- Step 4: Final state check --- + // At the end, ownership should return to robj alone. + auto view = robj.view>(); + EXPECT_TRUE(view); + + const std::shared_ptr& node = view->get(); + ASSERT_TRUE(node.use_count() == 1); + } + ASSERT_TRUE(Node::instanceCount() == 0); + ASSERT_TRUE(Node::assertResourcesReleased()); + } + + + TEST(RObject_reflecting_shared_ptr, reflect_init_with_lvalue) + { + { + const int NUM = -1629; + std::shared_ptr nodePtr = std::make_shared(NUM); + { + RObject robj = reflect(nodePtr); + ASSERT_FALSE(robj.isEmpty()); + EXPECT_TRUE(nodePtr.use_count() == 2); + + // Check if RObject can reflect as `Node` + EXPECT_TRUE(robj.canViewAs()); + { + auto view = robj.view(); + ASSERT_TRUE(view); + + const Node& node = view->get(); + EXPECT_EQ(node.data(), NUM); + // Ensure no copy is made for viewing. + EXPECT_TRUE(Node::instanceCount() == 1); + // Being shared by 'nodePtr' & 'robj'. + EXPECT_TRUE(nodePtr.use_count() == 2); + } + // Check if RObject can reflect as `shared_ptr` + EXPECT_TRUE(robj.canViewAs>()); + { + // Get a view of the view as `shared_ptr` + auto view = robj.view>(); + ASSERT_TRUE(view.has_value()); + { + std::shared_ptr node = view->get(); + EXPECT_EQ(node->data(), NUM); + //being shared by 'nodePtr', 'robj' and 'node'. + EXPECT_TRUE(nodePtr.use_count() == 3); + } { + const std::shared_ptr& node = view->get(); + EXPECT_EQ(node->data(), NUM); + //being shared by 'nodePtr', 'robj' and 'node'. + EXPECT_TRUE(nodePtr.use_count() == 2); + } + } + //still shared by 'nodePtr' & 'robj'. + EXPECT_TRUE(nodePtr.use_count() == 2); + } + //now owned by 'uptr' alone. + EXPECT_TRUE(nodePtr.use_count() == 1); + } + EXPECT_TRUE(Node::instanceCount() == 0); + EXPECT_TRUE(Node::assertResourcesReleased()); + } + + + TEST(RObject_reflecting_shared_ptr, reflect_init_with_rvalue) + { + { + constexpr const int NUM = 6839; + RObject robj = reflect(std::make_shared(NUM)); + ASSERT_FALSE(robj.isEmpty()); + + EXPECT_TRUE(Node::instanceCount() == 1); + // Check if RObject can reflect as `shared_ptr` + EXPECT_TRUE(robj.canViewAs>()); + //view just holds ref/ptr. + auto view = robj.view>(); + ASSERT_TRUE(view.has_value()); + + const std::shared_ptr& sptrNode = view->get(); //no copy + EXPECT_EQ(sptrNode->data(), NUM); + { + std::shared_ptr sptrNode0 = view->get(); + //owned by 'robj' & sptrNode0. + EXPECT_TRUE(sptrNode.use_count() == 2); + } + //owned by 'robj' alone, no 'lvalue' exists in this scope. + EXPECT_TRUE(sptrNode.use_count() == 1); + EXPECT_TRUE(robj.canViewAs()); + { + auto view = robj.view(); + ASSERT_TRUE(view); + + const Node& node = view->get(); + EXPECT_EQ(node.data(), NUM); + //owned by 'robj' alone. + EXPECT_TRUE(sptrNode.use_count() == 1); + } + } + EXPECT_TRUE(Node::instanceCount() == 0); + EXPECT_TRUE(Node::assertResourcesReleased()); + } + + + TEST(RObject_reflecting_shared_ptr, move_semantics_pod) + { + constexpr const int NUM = 25738; + RObject robj = reflect(std::make_shared(NUM)); + ASSERT_FALSE(robj.isEmpty()); + + // Check if RObject can reflect as `shared_ptr` + EXPECT_TRUE(robj.canViewAs>()); + { + // Get a view of the value as `shared_ptr` + auto view = robj.view>(); + // Ensure the view is valid + ASSERT_TRUE(view.has_value()); + { + std::shared_ptr sptrVal = view->get(); + + EXPECT_EQ(*sptrVal, NUM); + //being shared by robj & sptrVal. + EXPECT_TRUE(sptrVal.use_count() == 2); + } + //here owned by 'robj' alone. + EXPECT_TRUE(view->get().use_count() == 1); + } { + //create copy of RObject itself. + RObject robj0 = std::move(robj); + //robj should be empty now. + ASSERT_TRUE(robj.isEmpty()); + + auto view = robj0.view>(); + ASSERT_TRUE(view.has_value()); + { + const std::shared_ptr& sptrVal = view->get(); + + EXPECT_EQ(*sptrVal, NUM); + //single owner now, just robj0. + EXPECT_TRUE(sptrVal.use_count() == 1); + } { + //copy of shared_ptr got created. + std::shared_ptr sptrVal = view->get(); + + EXPECT_EQ(*sptrVal, NUM); + //being shared by two entities- robj0 & sptrVal. + EXPECT_TRUE(sptrVal.use_count() == 2); + } + //now owned by 'robj0' alone. + EXPECT_TRUE(view->get().use_count() == 1); + } + } + + + TEST(RObject_reflecting_shared_ptr, move_semantics_Node) + { + { + constexpr const int NUM = -15442; + RObject robj = reflect(std::make_shared(NUM)); + ASSERT_FALSE(robj.isEmpty()); + EXPECT_TRUE(robj.canViewAs>()); + { + // Get a view of the value as `shared_ptr` + auto view = robj.view>(); + // Ensure the view is valid + ASSERT_TRUE(view.has_value()); + { + std::shared_ptr sptrNode = view->get(); + EXPECT_EQ(sptrNode->data(), NUM); + // Being shared by robj & sptrVal. + EXPECT_TRUE(sptrNode.use_count() == 2); + } + // Here owned by 'robj' alone. + EXPECT_TRUE(view->get().use_count() == 1); + } { + //create copy of RObject itself. + RObject robj0 = std::move(robj); + //robj should be empty now. + ASSERT_TRUE(robj.isEmpty()); + + auto view = robj0.view>(); + ASSERT_TRUE(view.has_value()); + { + const std::shared_ptr& sptrNode = view->get(); + + EXPECT_EQ(sptrNode->data(), NUM); + //single owner now, just robj0. + EXPECT_TRUE(sptrNode.use_count() == 1); + } { + //copy of shared_ptr got created. + std::shared_ptr sptrNode = view->get(); + + EXPECT_EQ(sptrNode->data(), NUM); + //being shared by two entities- robj0 & sptrVal. + EXPECT_TRUE(sptrNode.use_count() == 2); + } + //now owned by 'robj0' alone. + EXPECT_TRUE(view->get().use_count() == 1); + } + } + EXPECT_TRUE(Node::instanceCount() == 0); + EXPECT_TRUE(Node::assertResourcesReleased()); + } + + + TEST(RObject_reflecting_shared_ptr, reflect_and_create_copies) + { + { + constexpr const int NUM = 10742; + RObject robj = reflect(std::make_shared(NUM)); + + error reterr = cxx_mirror().setupCloning(robj); + ASSERT_TRUE(reterr == error::None); + + ASSERT_FALSE(robj.isEmpty()); + EXPECT_TRUE(robj.canViewAs>()); + + auto view = robj.view>(); + ASSERT_TRUE(view.has_value()); + + // Access underlying value via the reflected shared_ptr + const std::shared_ptr& sptrNode = view->get(); + EXPECT_EQ(sptrNode->data(), NUM); + + // --------------------------------------------------------------------- + // 1. Heap-clone of STL wrappers is forbidden. + // This prevents accidental deep copies of smart pointers that + // could violate ownership semantics (e.g. double-deletion). + // --------------------------------------------------------------------- + auto [err, badObj] = robj.clone(); + EXPECT_TRUE(err == error::StlWrapperHeapAllocForbidden); + ASSERT_TRUE(badObj.isEmpty()); + + // --------------------------------------------------------------------- + // 2. clone using 'entity::Value': tries to copy the contained entity. + // Since Node is explicitly non-copyable, this yields an error. + // --------------------------------------------------------------------- + { + auto [err0, robj0] = robj.clone(); + EXPECT_TRUE(err0 == error::TypeNotCopyConstructible); + } + + // --------------------------------------------------------------------- + // 3. Explicit clone of the wrapper (entity::Wrapper): + // This performs a shallow copy of std::shared_ptr, incrementing the + // reference count while leaving the Node untouched. + // This demonstrates how RTL allows smart pointer semantics to be + // preserved even when the pointee type itself is non-copyable. + // --------------------------------------------------------------------- + { + auto [err0, robj0] = robj.clone(); + EXPECT_TRUE(err0 == error::None); + + auto view = robj0.view>(); + ASSERT_TRUE(view.has_value()); + + const std::shared_ptr& sptrNode0 = view->get(); + EXPECT_EQ(sptrNode0->data(), NUM); + { + // Making another copy of shared_ptr, still shallow (reference-counted) + std::shared_ptr sptrNode1 = view->get(); + EXPECT_EQ(sptrNode0->data(), NUM); + // Now shared by three entities: robj, robj0, and sptrNode1 + EXPECT_TRUE(sptrNode.use_count() == 3); + } + // Back to two owners: robj and robj0 + EXPECT_TRUE(sptrNode.use_count() == 2); + } + + // Finally, back to sole ownership by robj + EXPECT_TRUE(sptrNode.use_count() == 1); + EXPECT_TRUE(Node::instanceCount() == 1); + } + + // After leaving scope: no leaks, all resources released + EXPECT_TRUE(Node::instanceCount() == 0); + EXPECT_TRUE(Node::assertResourcesReleased()); + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/RObjectTests/RObjectReflecting_stdUniquePtr.cpp b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_stdUniquePtr.cpp new file mode 100644 index 00000000..5ff37e92 --- /dev/null +++ b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_stdUniquePtr.cpp @@ -0,0 +1,481 @@ + +#include +#include + +#include "Node.h" +#include "RTLibInterface.h" + +using namespace test_utils; +using namespace rtl; + +namespace rtl::unit_test +{ + TEST(RObject_reflecting_unique_ptr, clone_on__heap_stack) + { + const int NUM = 43728; + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + { + RObject robj0 = reflect(std::make_unique(NUM)); + ASSERT_FALSE(robj0.isEmpty()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + { + auto [err, robj] = robj0.clone(); + EXPECT_TRUE(err == error::TypeNotCopyConstructible); + ASSERT_TRUE(robj.isEmpty()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + } { + auto [err, robj] = robj0.clone(); + EXPECT_TRUE(err == error::StlWrapperHeapAllocForbidden); + ASSERT_TRUE(robj.isEmpty()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + } + } + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(RObject_reflecting_unique_ptr, lvalue_no_double_delete) + { + { + constexpr int NUM = 1635; + int* numPtr = new int(NUM); + std::unique_ptr srcPtr(numPtr); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + + // Reflect a move-only type directly into RObject + RObject robj = reflect(std::move(srcPtr)); + ASSERT_FALSE(robj.isEmpty()); + + EXPECT_TRUE(robj.canViewAs>()); + // unique_ptr, ownership managed by RTL. + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + + auto view = robj.view>(); + ASSERT_TRUE(view); + + // get() - moves out the 'unique_ptr' from 'robj'. + // 'robj' still remains alive and type-consistent, but now holds an empty unique_ptr + std::unique_ptr uptr = view->get(); + ASSERT_TRUE(uptr); + // RTL gave up the ownership after move-op. + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + // Access the moved-out value + EXPECT_EQ(*uptr, NUM); + + int* ptr = uptr.release(); + // Addresses must be same. + EXPECT_EQ(numPtr, ptr); + delete ptr; //RTL must not delete again, once 'robj' out of scope. + } + // there must not be any crash. + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(RObject_reflecting_unique_ptr, rvalue_no_double_delete) + { + { + int NUM = 5323; + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + + // Reflect a move-only type directly into RObject + RObject robj = reflect(std::unique_ptr(new Node(NUM))); + ASSERT_FALSE(robj.isEmpty()); + + EXPECT_TRUE(robj.canViewAs>()); + // unique_ptr, ownership managed by RTL. + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + + auto view = robj.view>(); + ASSERT_TRUE(view); + + // get() - moves out the 'unique_ptr' from 'robj'. + // 'robj' still remains alive and type-consistent, but now holds an empty unique_ptr + std::unique_ptr uptr = view->get(); + ASSERT_TRUE(uptr); + // RTL gave up the ownership after move-op. + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + // Access the moved-out value + EXPECT_EQ(uptr->data(), NUM); + + Node* ptr = uptr.release(); + // Addresses must be same. + EXPECT_TRUE(ptr->data() == NUM); + delete ptr; //RTL must not delete again, once 'robj' out of scope. + } + // there must not be any crash. + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(RObject_reflecting_unique_ptr, pod_init_with_lvalue) + { + constexpr const int NUM = 8839; + std::unique_ptr uptr = std::make_unique(NUM); + // No heap leaks- ownership accounting is perfect + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + + // Reflect a move-only type directly into RObject + RObject robj = reflect(std::move(uptr)); + ASSERT_FALSE(robj.isEmpty()); + + // RObject can transparently expose the pointee type + EXPECT_TRUE(robj.canViewAs()); + + auto intView = robj.view(); + ASSERT_TRUE(intView); + + const int& valueNum = intView->get(); + EXPECT_EQ(valueNum, NUM); + + // RObject can also reflect as the original move-only type + EXPECT_TRUE(robj.canViewAs>()); + + // Multiple independent views to the same stored object- all valid before a move + auto view0 = robj.view>(); + ASSERT_TRUE(view0); + + auto view1 = robj.view>(); + ASSERT_TRUE(view1); + + auto view2 = robj.view>(); + ASSERT_TRUE(view2); + + // unique_ptr, ownership managed by RTL. + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + + // A move from any view transfers ownership of the underlying object + // RObject remains alive and type-consistent, but now holds an empty unique_ptr + std::unique_ptr uptr0 = view0->get(); + ASSERT_TRUE(uptr0); + + // Access the moved-out value + EXPECT_EQ(*uptr0, NUM); + + // Verify the original pointer address matches + EXPECT_EQ(uptr0.get(), &valueNum); + + // RTL gave up the ownership after move-op. + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + + // RObject still exists with correct metadata, but the stored unique_ptr is now empty + ASSERT_FALSE(robj.isEmpty()); + + // Any subsequent view will still be obtainable + auto view3 = robj.view>(); + ASSERT_TRUE(view3); + + // But the unique_ptr inside is now empty due to the earlier move + std::unique_ptr uptr3 = view3->get(); + ASSERT_TRUE(uptr3 == nullptr); + + // All earlier views now yield empty unique_ptrs as well- no dangling pointers, no UB + std::unique_ptr uptr2 = view2->get(); + ASSERT_TRUE(uptr3 == nullptr); + + std::unique_ptr uptr1 = view1->get(); + ASSERT_TRUE(uptr3 == nullptr); + + // Even reusing the moved-from view0 is safe- just returns empty + std::unique_ptr uptr00 = view0->get(); + ASSERT_TRUE(uptr00 == nullptr); + } + + + TEST(RObject_reflecting_unique_ptr, init_with_lvalue) + { + constexpr const int NUM = 16238; + std::unique_ptr uptr = std::make_unique(NUM); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + // Reflect a move-only type directly into RObject + RObject robj = reflect(std::move(uptr)); + ASSERT_FALSE(robj.isEmpty()); + + // RObject can transparently expose the pointee type + EXPECT_TRUE(robj.canViewAs()); + + auto nodeView = robj.view(); + ASSERT_TRUE(nodeView); + + const Node& node = nodeView->get(); + EXPECT_EQ(node.data(), NUM); + + // RObject can also reflect as the original move-only type + EXPECT_TRUE(robj.canViewAs>()); + { + // Multiple independent views to the same stored object- all valid before a move + auto view0 = robj.view>(); + ASSERT_TRUE(view0); + + auto view1 = robj.view>(); + ASSERT_TRUE(view1); + + auto view2 = robj.view>(); + ASSERT_TRUE(view2); + + // unique_ptr, ownership managed by RTL. + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 1); + + // A move from any view transfers ownership of the underlying object + // RObject remains alive and type-consistent, but now holds an empty unique_ptr + std::unique_ptr uptr0 = view0->get(); + ASSERT_TRUE(uptr0); + + // Access the moved-out value + EXPECT_EQ(uptr0->data(), NUM); + + // Verify the original pointer address matches + EXPECT_EQ(uptr0.get(), &node); + + // RObject still exists with correct metadata, but the stored unique_ptr is now empty + ASSERT_FALSE(robj.isEmpty()); + + // RTL gave up the ownership after move-op. + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + // Node still exists. + EXPECT_TRUE(Node::instanceCount() == 1); + + // Any subsequent view will still be obtainable + auto view3 = robj.view>(); + ASSERT_TRUE(view3); + + // But the unique_ptr inside is now empty due to the earlier move + std::unique_ptr uptr3 = view3->get(); + ASSERT_TRUE(uptr3 == nullptr); + + // All earlier views now yield empty unique_ptrs as well- no dangling pointers, no UB + std::unique_ptr uptr2 = view2->get(); + ASSERT_TRUE(uptr3 == nullptr); + + std::unique_ptr uptr1 = view1->get(); + ASSERT_TRUE(uptr3 == nullptr); + + // Even reusing the moved-from view0 is safe- just returns empty + std::unique_ptr uptr00 = view0->get(); + ASSERT_TRUE(uptr00 == nullptr); + } + EXPECT_TRUE(Node::instanceCount() == 0); + EXPECT_TRUE(Node::assertResourcesReleased()); + } + + + + TEST(RObject_reflecting_unique_ptr, destructor_call__reflectecting_lvalue) + { + const int NUM = 24028; + std::unique_ptr nodePtr = std::make_unique(NUM); + RObject robj = reflect(nodePtr); + ASSERT_FALSE(robj.isEmpty()); + + // Check if RObject can reflect as `Node` + EXPECT_TRUE(robj.canViewAs()); + { + auto view = robj.view(); + ASSERT_TRUE(view); + + const Node& node = view->get(); + EXPECT_EQ(node.data(), NUM); + // Ensure no copy is made for viewing. + EXPECT_TRUE(Node::instanceCount() == 1); + } + // Check if RObject can reflect as `shared_ptr` + EXPECT_FALSE(robj.canViewAs>()); + { + // Get a view of the view as `shared_ptr` + auto view = robj.view>(); + ASSERT_FALSE(view); + } + // Check if RObject can reflect as `unique_ptr` + EXPECT_TRUE(robj.canViewAs>()); + { + // Get a view of the view as `shared_ptr` + auto view = robj.view>(); + EXPECT_TRUE(view); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() != 0); + { + std::unique_ptr movedOutPtr = view->get(); + } + EXPECT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + EXPECT_TRUE(Node::instanceCount() == 0); + EXPECT_TRUE(Node::assertResourcesReleased()); + } + } + + + TEST(RObject_reflecting_unique_ptr, destructor_call__reflectecting_rvalue) + { + const int NUM = 28228; + RObject robj = reflect(std::make_unique(NUM)); + ASSERT_FALSE(robj.isEmpty()); + + // Check if RObject can reflect as `Node` + EXPECT_TRUE(robj.canViewAs()); + { + auto view = robj.view(); + ASSERT_TRUE(view); + + const Node& node = view->get(); + EXPECT_EQ(node.data(), NUM); + // Ensure no copy is made for viewing. + EXPECT_TRUE(Node::instanceCount() == 1); + } + // Check if RObject can reflect as `shared_ptr` + EXPECT_FALSE(robj.canViewAs>()); + { + // Get a view of the view as `shared_ptr` + auto view = robj.view>(); + ASSERT_FALSE(view); + } + // Check if RObject can reflect as `unique_ptr` + EXPECT_TRUE(robj.canViewAs>()); + { + // Get a view of the view as `shared_ptr` + auto view = robj.view>(); + EXPECT_TRUE(view); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() != 0); + { + std::unique_ptr movedOutPtr = view->get(); + } + EXPECT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + EXPECT_TRUE(Node::instanceCount() == 0); + EXPECT_TRUE(Node::assertResourcesReleased()); + } + } + + + TEST(RObject_reflecting_unique_ptr, init_with_rvalue) + { + { + constexpr const int NUM = 32443; + RObject robj = reflect(std::make_unique(NUM)); + ASSERT_FALSE(robj.isEmpty()); + + EXPECT_TRUE(Node::instanceCount() == 1); + EXPECT_TRUE(robj.canViewAs()); + { + auto view = robj.view(); + ASSERT_TRUE(view); + + const Node& node = view->get(); + EXPECT_EQ(node.data(), NUM); + } + // Check if RObject can be viewed as `unique_ptr` + EXPECT_TRUE(robj.canViewAs>()); + { + auto view = robj.view>(); + ASSERT_TRUE(view); + ASSERT_FALSE(robj.isEmpty()); + + std::unique_ptr uptrNode = std::move(view->get()); + EXPECT_EQ(uptrNode->data(), NUM); + } + } + EXPECT_TRUE(Node::instanceCount() == 0); + EXPECT_TRUE(Node::assertResourcesReleased()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(RObject_reflecting_unique_ptr, init_with_const_lvalue) + { + { + const int NUM = 35729; + std::unique_ptr nodePtr = std::make_unique(NUM); + { + RObject robj = reflect(nodePtr); + ASSERT_FALSE(robj.isEmpty()); + + // Check if RObject can reflect as `Node` + EXPECT_TRUE(robj.canViewAs()); + { + auto view = robj.view(); + ASSERT_TRUE(view); + + const Node& node = view->get(); + EXPECT_EQ(node.data(), NUM); + // Ensure no copy is made for viewing. + EXPECT_TRUE(Node::instanceCount() == 1); + } + // Check if RObject can reflect as `unique_ptr` + EXPECT_FALSE(robj.canViewAs>()); + { + // Get a view of the view as `unique_ptr` + auto view = robj.view>(); + ASSERT_FALSE(view); + } + // Check if RObject can reflect as `unique_ptr` + EXPECT_TRUE(robj.canViewAs>()); + { + // Get a view of the view as `unique_ptr` + auto view = robj.view>(); + EXPECT_TRUE(view); + } + } + } + EXPECT_TRUE(Node::instanceCount() == 0); + EXPECT_TRUE(Node::assertResourcesReleased()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + + TEST(RObject_reflecting_unique_ptr, init_with_const_rvalue) + { + { + const int NUM = 39929; + RObject robj = reflect(std::make_unique(NUM)); + ASSERT_FALSE(robj.isEmpty()); + + // Check if RObject can reflect as `Node` + EXPECT_TRUE(robj.canViewAs()); + { + auto view = robj.view(); + ASSERT_TRUE(view); + + const Node& node = view->get(); + EXPECT_EQ(node.data(), NUM); + // Ensure no copy is made for viewing. + EXPECT_TRUE(Node::instanceCount() == 1); + } + // Check if RObject can reflect as `unique_ptr` + EXPECT_FALSE(robj.canViewAs>()); + { + // Get a view of the view as `unique_ptr` + auto view = robj.view>(); + ASSERT_FALSE(view); + } + // Check if RObject can reflect as `unique_ptr` + EXPECT_TRUE(robj.canViewAs>()); + { + // Get a view of the view as `unique_ptr` + auto view = robj.view>(); + EXPECT_TRUE(view); + } + } + EXPECT_TRUE(Node::instanceCount() == 0); + EXPECT_TRUE(Node::assertResourcesReleased()); + ASSERT_TRUE(rtl::getRtlManagedHeapInstanceCount() == 0); + } + + TEST(RObject_reflecting_unique_ptr, create_clones) + { + const int NUM = 45429; + RObject robj = reflect(std::make_unique(NUM)); + ASSERT_FALSE(robj.isEmpty()); + + // Check if RObject can reflect as `Node` + EXPECT_TRUE(robj.canViewAs()); + { + auto view = robj.view(); + ASSERT_TRUE(view); + + const Node& node = view->get(); + EXPECT_EQ(node.data(), NUM); + // Ensure no copy is made for viewing. + EXPECT_TRUE(Node::instanceCount() == 1); + } { + auto [err, robj0] = robj.clone(); + EXPECT_TRUE(err == rtl::error::TypeNotCopyConstructible); + ASSERT_TRUE(robj0.isEmpty()); + } + } +} \ No newline at end of file diff --git a/RTLTestRunApp/src/RObjectTests/RObjectReflecting_strings.cpp b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_strings.cpp new file mode 100644 index 00000000..21d27632 --- /dev/null +++ b/RTLTestRunApp/src/RObjectTests/RObjectReflecting_strings.cpp @@ -0,0 +1,446 @@ + +#include + +#include "RTLibInterface.h" + +using namespace rtl; + +namespace +{ + static const std::string STR_STD_STRING = "string_type: std::string"; + static constexpr const char* STR_CONST_CHAR_POINTER = "string_type: const_char_*."; + + static char STR_CHAR_ARRAY[] = "string_type: const_char_array."; + static constexpr const char STR_CONST_CHAR_ARRAY[] = "string_type: const_char_array."; + + static const std::string_view STR_STD_STRING_VIEW = "string_type: std::string_view"; + + //initialize RTL, necessary for RObject conversions to work. + static auto _= rtl::CxxMirror({/*..empty..*/}); +} + + +namespace rtl_tests +{ + + TEST(RObject_view_negative_test, disallowed_mutable_views_should_not_compile) + { + RObject robj = rtl::reflect(std::string("Immutable")); + + /* The following lines SHOULD NOT COMPILE if uncommented: + These are intentionally commented to enforce design-time correctness. + */ + + /* ASSERT_FALSE(robj.canViewAs()); //Mutable pointer not allowed + ASSERT_FALSE(robj.canViewAs()); //Mutable C-string + ASSERT_FALSE(robj.canViewAs()); //Reference not supported + ASSERT_FALSE(robj.canViewAs()); //Rvalue ref not allowed + + auto bad1 = robj.view(); //Mutable pointer not allowed + auto bad2 = robj.view(); //Mutable C-string + auto bad3 = robj.view(); //Reference not supported + auto bad4 = robj.view(); //Rvalue ref not allowed + */ + } + + + TEST(RObject_view_negative_test, incompatible_view_returns_nullopt) + { + RObject robj = rtl::reflect(std::string("test")); + + ASSERT_FALSE(robj.canViewAs()); + + // Request a view of an incompatible type + auto view = robj.view(); + ASSERT_FALSE(view.has_value()); // Must return nullopt + } + + + TEST(RObject_view_negative_test, incompatible_reflected_type_returns_nullopt) + { + int value = 42; + RObject robj = rtl::reflect(&value); + + // Although value is stored, it's not a string + ASSERT_FALSE(robj.canViewAs()); + auto view0 = robj.view(); + ASSERT_FALSE(view0.has_value()); + + ASSERT_FALSE(robj.canViewAs()); + auto view1 = robj.view(); + ASSERT_FALSE(view1.has_value()); + } +} + + +namespace unit_test +{ + TEST(RObject_init_with_stdString_pointer, view_as_std_string) + { + // Create an RObject that reflects a std::string pointer. + RObject robj = rtl::reflect(&STR_STD_STRING); + + // Check if the value can be accessed as 'std::string'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string' and verify it is present. + auto view = robj.view(); + ASSERT_TRUE(view.has_value()); + + const std::string& str_cref = view->get(); + + // Validate the addresses are same, no copy made. + ASSERT_EQ(&str_cref, &STR_STD_STRING); + } + + + TEST(RObject_init_with_stdString_pointer, view_as_const_char_ptr) + { + // Create an RObject that reflects a std::string pointer. + RObject robj = rtl::reflect(&STR_STD_STRING); + + // Check if the value can be accessed as 'const char*'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'const char*' and verify it is present. + auto view = robj.view(); + ASSERT_TRUE(view.has_value()); + + // Ensure the returned pointer is the original std::string's buffer (no copy). + const char& str_cref = view->get(); + ASSERT_EQ(&str_cref, STR_STD_STRING.c_str()); + } + + + TEST(RObject_init_with_stdString_pointer, view_as_std_string_view) + { + // Create an RObject that reflects a std::string pointer. + RObject robj = rtl::reflect(&STR_STD_STRING); + + // Check if the value can be accessed as 'std::string_view'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string_view' and verify it is present. + auto view = robj.view(); + ASSERT_TRUE(view.has_value()); + + // Validate the string_view content matches the original input. + const std::string_view& str_cref = view->get(); + ASSERT_EQ(str_cref, STR_STD_STRING); + } + + + TEST(RObject_view_as_std_string_and_string_view, init_with_empty_literal) + { + // Create an RObject that reflects a empty string literal rvalue + RObject robj = rtl::reflect(""); + + // Check if the value can be accessed as 'std::string_view'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string_view' and verify it is present. + auto view0 = robj.view(); + ASSERT_TRUE(view0.has_value()); + + // Validate the 'string_view' content matches the original input. + const std::string_view& str_view = view0->get(); + ASSERT_EQ(str_view, ""); + + // Check if the value can be accessed as 'std::string'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string' and verify it is present. + auto view1 = robj.view(); + ASSERT_TRUE(view1.has_value()); + + // Validate the string content matches the original input. + const std::string& str_cref = view1->get(); + ASSERT_EQ(str_cref, ""); + } + + + TEST(RObject_view_as_std_string_and_string_view, init_with_charArray) + { + // Create an RObject that reflects a string value (init with 'char[]'). + RObject robj = rtl::reflect(STR_CHAR_ARRAY); + + // Check if the value can be accessed as 'std::string_view'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string_view' and verify it is present. + auto view0 = robj.view(); + ASSERT_TRUE(view0.has_value()); + + // Validate the string content matches the original input. + const std::string_view& str_view = view0->get(); + ASSERT_EQ(str_view, STR_CHAR_ARRAY); + + // Check if the value can be accessed as 'std::string'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string' and verify it is present. + auto view1 = robj.view(); + ASSERT_TRUE(view1.has_value()); + + // Validate the string content matches the original input. + const std::string& str_cref = view1->get(); + ASSERT_EQ(str_cref, STR_CHAR_ARRAY); + + // Check if the value can be accessed as 'const char*'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'const char*' and verify it is present. + auto view2 = robj.view(); + ASSERT_TRUE(view2.has_value()); + + //since the char[] is wrapped in string_view, base address is stored, data not copied. + const char& str_addr = view2->get(); + ASSERT_EQ(&str_addr, STR_CHAR_ARRAY); + } + + + TEST(RObject_init_with_charArray, view_as_const_char_ptr) + { + // Create an RObject that reflects a string value (init with 'char[]'). + RObject robj = rtl::reflect(STR_CHAR_ARRAY); + + // Check if the value can be accessed as 'const char*'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'const char*' and verify it is present. + auto view = robj.view(); + ASSERT_TRUE(view.has_value()); + + const char& str_cref = view->get(); + // Ensure the returned pointer is the original array (no copy). + ASSERT_EQ(&str_cref, STR_CHAR_ARRAY); + // Validate the string content. + ASSERT_EQ(std::string_view(&str_cref), std::string_view(STR_CHAR_ARRAY)); + } + + + TEST(RObject_view_as_std_string_and_string_view, init_with_constCharArray) + { + // Create an RObject that reflects a string value (init with 'const char[]'). + RObject robj = rtl::reflect(STR_CONST_CHAR_ARRAY); + + // Check if the value can be accessed as 'std::string_view'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string_view' and verify it is present. + auto view0 = robj.view(); + ASSERT_TRUE(view0.has_value()); + + // Validate the string content matches the original input. + const std::string_view& str_view = view0->get(); + ASSERT_EQ(str_view, STR_CONST_CHAR_ARRAY); + + // Check if the value can be accessed as 'std::string'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string' and verify it is present. + auto view1 = robj.view(); + ASSERT_TRUE(view1.has_value()); + + //since the char[] is wrapped in string_view, but will return std::string copy. + const std::string& str_cref = view1->get(); + ASSERT_EQ(str_cref, STR_CONST_CHAR_ARRAY); + + // Check if the value can be accessed as 'const char*'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'const char*' and verify it is present. + auto view2 = robj.view(); + ASSERT_TRUE(view2.has_value()); + + //since the char[] is wrapped in string_view, base address is stored, data not copied. + const char& str_addr = view2->get(); + ASSERT_EQ(&str_addr, STR_CONST_CHAR_ARRAY); + } + + + TEST(RObject_view_as_const_char_ptr, init_with_constCharArray) + { + // Create an RObject that reflects a string value (init with 'const char[]'). + RObject robj = rtl::reflect(STR_CONST_CHAR_ARRAY); + + // Check if the value can be accessed as 'const char*'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'const char*' and verify it is present. + auto view = robj.view(); + ASSERT_TRUE(view.has_value()); + + const char& str_cref = view->get(); + + //the addresses are same + ASSERT_EQ(&str_cref, STR_CONST_CHAR_ARRAY); + + // Validate the C-string content matches the original input. + ASSERT_EQ(std::string_view(&str_cref), STR_CONST_CHAR_ARRAY); + } + + + TEST(RObject_view_as_std_string_and_string_view, init_with_constCharPtr) + { + // Create an RObject that reflects a string value (init with 'const char*'). + RObject robj = rtl::reflect(STR_CONST_CHAR_POINTER); + + // Check if the value can be accessed as 'std::string'. + ASSERT_FALSE(robj.canViewAs()); + + // Check if the value can be accessed as 'std::string'. + ASSERT_FALSE(robj.canViewAs()); + + // Check if the value can be accessed as 'std::string'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string' and verify it is present. + auto view = robj.view(); + ASSERT_TRUE(view.has_value()); + + // Validate the string content matches the original input. + const char& str_addr = view->get(); + ASSERT_EQ(&str_addr, STR_CONST_CHAR_POINTER); + } + + + TEST(RObject_init_with_stdString, view_as_std_string) + { + // Create an RObject that reflects a string value (init with 'std::string'). + RObject robj = rtl::reflect(STR_STD_STRING); + + // Check if the value can be accessed as 'std::string'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string' and verify it is present. + auto view0 = robj.view(); + ASSERT_TRUE(view0.has_value()); + + // Validate the string content matches the original input. + const std::string& str_cref = view0->get(); + ASSERT_EQ(str_cref, STR_STD_STRING); + + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'const char*' and verify it is present. + auto view1 = robj.view(); + ASSERT_TRUE(view1.has_value()); + + // Validate the base address are different, since RObject is reflecting a copy. + const char& str_addr = view1->get(); + ASSERT_NE(&str_addr, STR_STD_STRING.c_str()); + } + + + TEST(RObject_init_with_stdString_rvalue, view_as_std_string) + { + // Create an RObject that reflects a string value (init with 'std::string' rvalue). + RObject robj = rtl::reflect(std::string(STR_STD_STRING)); + + // Check if the value can be accessed as 'std::string'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string' and verify it is present. + auto view = robj.view(); + ASSERT_TRUE(view.has_value()); + + // Validate the string content matches the original input. + const std::string& str_cref = view->get(); + ASSERT_EQ(str_cref, STR_STD_STRING); + } + + + TEST(RObject_init_with_stdString, view_as_std_string_view) + { + // Create an RObject that reflects a string value (init with 'std::string'). + RObject robj = rtl::reflect(STR_STD_STRING); + + // Check if the value can be accessed as 'std::string_view'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string_view' and verify it is present. + auto view = robj.view(); + ASSERT_TRUE(view.has_value()); + + // Validate the string_view content matches the original input. + const std::string_view& str_cref = view->get(); + ASSERT_EQ(str_cref, STR_STD_STRING); + } + + + TEST(RObject_init_with_stdStringView, view_as_std_string) + { + // Create an RObject that reflects a string value (init with 'std::string_view'). + // Stores a copy of the 'std::string_view' as a 'std::string'. + RObject robj = rtl::reflect(STR_STD_STRING_VIEW); + + // Check if the value can be accessed as 'std::string'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string' and verify it is present. + auto view = robj.view(); + ASSERT_TRUE(view.has_value()); + + // Validate the string content matches the original input. + const std::string& str_cref = view->get(); + ASSERT_EQ(str_cref, STR_STD_STRING_VIEW); + } + + + TEST(RObject_init_with_stdStringView, view_as_std_string_view) + { + // Create an RObject that reflects a string value (init with 'std::string_view'). + // Stores a copy of the 'std::string_view' as a 'std::string'. + RObject robj = rtl::reflect(STR_STD_STRING_VIEW); + + // Check if the value can be accessed as 'std::string_view'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string_view' and verify it is present. + auto view = robj.view(); + ASSERT_TRUE(view.has_value()); + + // Validate the string_view content matches the original input. + const std::string_view& str_cref = view->get(); + ASSERT_EQ(str_cref, STR_STD_STRING_VIEW); + } + + + TEST(RObject_init_with_stdStringView_rvalue, view_as_std_string_view) + { + // Create an RObject that reflects a string value (init with 'std::string_view'). + // Stores a copy of the 'std::string_view' as a 'std::string'. + RObject robj = rtl::reflect(std::string_view(STR_CONST_CHAR_POINTER)); + + // Check if the value can be accessed as 'std::string_view'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'std::string_view' and verify it is present. + auto view = robj.view(); + ASSERT_TRUE(view.has_value()); + + // Validate the string_view content matches the original input. + const std::string_view& str_cref = view->get(); + ASSERT_EQ(str_cref, STR_CONST_CHAR_POINTER); + } + + + TEST(RObject_init_with_stdStringView, view_as_const_char_ptr) + { + // Create an RObject that reflects a string value (init with 'std::string_view'). + // Stores a copy of the 'std::string_view' as a 'std::string'. + RObject robj = rtl::reflect(STR_STD_STRING_VIEW); + + // Check if the value can be accessed as 'const char*'. + ASSERT_TRUE(robj.canViewAs()); + + // Try to obtain a view as 'const char*' and verify it is present. + auto view = robj.view(); + ASSERT_TRUE(view.has_value()); + + // Validate the C-string content matches the original input. + const char& str_cref = view->get(); + ASSERT_EQ(std::string_view(&str_cref), STR_STD_STRING_VIEW); + } +} diff --git a/RTLTestRunApp/src/main.cpp b/RTLTestRunApp/src/main.cpp new file mode 100644 index 00000000..9d1f253d --- /dev/null +++ b/RTLTestRunApp/src/main.cpp @@ -0,0 +1,38 @@ + +#include +#include +#include "CxxMirrorTests/CxxMirrorThreadingTest.h" + + +class GlobalTestEnvironment : public ::testing::Environment +{ +public: + void SetUp() override + { + std::cout << "\n----------------------------------------------------------------------"; + std::cout << "\n CxxMirror ==> Initialization & Multithreading-Test "; + std::cout << "\n----------------------------------------------------------------------"; + { + std::jthread t0(rtl_tests::InitMirror::reflectingBook); + std::jthread t1(rtl_tests::InitMirror::reflectingDate); + std::jthread t2(rtl_tests::InitMirror::reflectingEvent); + std::jthread t3(rtl_tests::InitMirror::reflectingAnimal); + std::jthread t4(rtl_tests::InitMirror::reflectingPerson); + std::jthread t5(rtl_tests::InitMirror::reflectingLibrary); + std::jthread t6(rtl_tests::InitMirror::reflectingPodsStl); + std::jthread t7(rtl_tests::InitMirror::reflectingCalender); + std::jthread t8(rtl_tests::InitMirror::reflectingMyTypePerson); + std::jthread t9(rtl_tests::InitMirror::reflectingCStyleFunctions); + } + std::cout << "\n----------------------------------------------------------------------\n\n"; + } +}; + + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(new GlobalTestEnvironment); + + return RUN_ALL_TESTS(); +} diff --git a/ReflectionTemplateLib/CMakeLists.txt b/ReflectionTemplateLib/CMakeLists.txt index dcc251ee..894d02b8 100644 --- a/ReflectionTemplateLib/CMakeLists.txt +++ b/ReflectionTemplateLib/CMakeLists.txt @@ -1,23 +1,28 @@ # CMakeLists.txt for ReflectionTemplateLib -# Set the minimum required CMake version cmake_minimum_required(VERSION 3.20) -# Set the project name -project(ReflectionTemplateLib) +# Project definition +project(ReflectionTemplateLib LANGUAGES CXX) +# Require C++20 set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) -SET(CXX_LIB_NAME ReflectionTemplateLib) +# Library target +add_library(${PROJECT_NAME} STATIC) -ADD_LIBRARY(${PROJECT_NAME} STATIC "") +# Public include directories +target_include_directories(${PROJECT_NAME} + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/common + ${CMAKE_CURRENT_SOURCE_DIR}/detail/inc + ${CMAKE_CURRENT_SOURCE_DIR}/access/inc + ${CMAKE_CURRENT_SOURCE_DIR}/builder/inc +) -INCLUDE_DIRECTORIES(common) -INCLUDE_DIRECTORIES(detail/inc) -INCLUDE_DIRECTORIES(access/inc) -INCLUDE_DIRECTORIES(builder/inc) - -# Add the source directory -INCLUDE(detail/src/CMakeLists.txt) -INCLUDE(access/src/CMakeLists.txt) -INCLUDE(builder/CMakeLists.txt) \ No newline at end of file +# Add subdirectories for sources +add_subdirectory(detail/src) +add_subdirectory(access/src) +add_subdirectory(builder) \ No newline at end of file diff --git a/ReflectionTemplateLib/access/inc/CxxMirror.h b/ReflectionTemplateLib/access/inc/CxxMirror.h index 81b49cde..82522e46 100644 --- a/ReflectionTemplateLib/access/inc/CxxMirror.h +++ b/ReflectionTemplateLib/access/inc/CxxMirror.h @@ -1,50 +1,70 @@ -#pragma once +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ -#include -#include -#include -#include "CxxReflection.h" +#pragma once -namespace rtl { +#include "CxxReflection.h" - namespace access +namespace rtl +{ + // Forward declarations + class Record; + class RObject; + class Function; + +/* @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. + * + * All type registrations happen during object construction. + * + * 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. + * + * All inherited members are properly destroyed when the object is destroyed, except for the *functor containers*. + * + * 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). + * + * Summary: + * - Functor objects are shared and static. + * - `Function` keys are per-instance. + * - Functor storage remains unaffected by the number of `CxxMirror` instances. +*/ + class CxxMirror : public detail::CxxReflection { - //forward decls - class Record; - class Function; - - /* @class: CxxMirror - * provides interface to access registered functions/methods by name. - * its the single point of access to whole reflection system. - * all the type registration is done while constructing its object. - * its objects can be createed locally and will be destroyed as regular object, at scope's end. - * deleted copy constructor and assignment operator, can only be passed around as reference or wrapped in a smart pointer. - * the inherited data members are freed upon destruction, except the 'functor-containers', they have static lifetime. - * 'functor-containers' are not member of this or base class, base only contains 'Function' objects which is a hash-key for looking up a particular functor. - * creating multiple objects of CxxMirror and registring the same functor will not increase the 'functor-container' size. - * once a functor is registered, no entry will be added to the 'functor-container' for the same functor. - * registering the same functor will create duplicate hash-key 'Function' object, which will be ignored if in the same 'CxxMirror' object. - if two different 'CxxMirror' objects are created and registering the same functor, the functor-container will have only one entry for the functor - but two identical 'Function' objects will be created, held by respective 'CxxMirror' object. - */ class CxxMirror : public detail::CxxReflection - { - public: - - //constructor, taking function objects, other constructors are disabled. - CxxMirror(const std::vector& pFunctions); - - //get the class/struct's member-functions hash-keys wrapped in a 'Record' object. - std::optional getRecord(const std::string& pRecordName) const; - - //get the non-member functions hash-keys. - std::optional getFunction(const std::string& pFunctionName) const; - - //get the class/struct's member-functions hash-keys wrapped in a 'Record' object, registered with a namespace name. - std::optional getRecord(const std::string& pNameSpaceName, const std::string& pRecordName) const; - - //get the non-member functions hash-keys, registered with a namespace name. - std::optional getFunction(const std::string& pNameSpaceName, const std::string& pFunctionName) const; - }; - } + public: + + // Constructs CxxMirror using a set of Function objects. All other constructors are disabled. + explicit CxxMirror(const std::vector& pFunctions); + + error setupCloning(const RObject& pTarget) const; + + // Returns a Record containing function hash-keys for the given record ID. + std::optional getRecord(const std::size_t pRecordId) const; + + // Returns a Record containing function hash-keys for the given record name. + std::optional getRecord(const std::string& pRecordName) const; + + // Returns a Record containing function hash-keys for the given record name (overloaded for namespace support). + std::optional getRecord(const std::string& pNameSpaceName, const std::string& pRecordName) const; + + // Returns a Function object for the given function name (non-member function). + std::optional getFunction(const std::string& pFunctionName) const; + + // Returns a Function object for the given function name, within the specified namespace. + std::optional getFunction(const std::string& pNameSpaceName, const std::string& pFunctionName) const; + }; } \ No newline at end of file diff --git a/ReflectionTemplateLib/access/inc/CxxMirror.hpp b/ReflectionTemplateLib/access/inc/CxxMirror.hpp new file mode 100644 index 00000000..baa4606e --- /dev/null +++ b/ReflectionTemplateLib/access/inc/CxxMirror.hpp @@ -0,0 +1,113 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#include "Record.h" +#include "Function.h" +#include "Method.h" +#include "CxxMirror.h" + + +namespace rtl +{ + +/* @Constructor: CxxMirror + @params: 'const std::vector&' + * 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. +*/ + inline CxxMirror::CxxMirror(const std::vector& pFunctions) + : detail::CxxReflection(pFunctions) + { + rtl::detail::ReflectedConversions::init(); + } + + +/* @method: getRecord + @param: const std::string& (name of the class/struct) + @return: std::optional + * 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. +*/ + inline std::optional CxxMirror::getRecord(const std::string& pRecord) const + { + return getRecord(std::string(detail::NAMESPACE_GLOBAL), pRecord); + } + + +/* @method: getFunction + @param: const std::string& (name of the non-member function) + @return: std::optional + * 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. +*/ + inline std::optional CxxMirror::getFunction(const std::string& pFunction) const + { + return getFunction(std::string(detail::NAMESPACE_GLOBAL), pFunction); + } + + + + inline std::optional CxxMirror::getRecord(const std::size_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 + * 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. +*/ + inline std::optional CxxMirror::getRecord(const std::string& pNameSpace, const std::string& pRecord) const + { + const auto& nsRecordMap = getNamespaceRecordMap(); + const auto& itr = nsRecordMap.find(pNameSpace); + if (itr != nsRecordMap.end()) + { + const auto& recordMap = itr->second; + const auto& itr0 = recordMap.find(pRecord); + 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 + * 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. +*/ + inline std::optional CxxMirror::getFunction(const std::string& pNameSpace, const std::string& pFunction) const + { + const auto& nsFunctionMap = getNamespaceFunctionsMap(); + const auto& itr = nsFunctionMap.find(pNameSpace); + if (itr != nsFunctionMap.end()) + { + const auto& functionMap = itr->second; + const auto& itr0 = functionMap.find(pFunction); + if (itr0 != functionMap.end()) { + return std::make_optional(itr0->second); + } + } + return std::nullopt; + } +} \ No newline at end of file diff --git a/ReflectionTemplateLib/access/inc/CxxMirrorToJson.h b/ReflectionTemplateLib/access/inc/CxxMirrorToJson.h index 40ab5fbf..f3e11771 100644 --- a/ReflectionTemplateLib/access/inc/CxxMirrorToJson.h +++ b/ReflectionTemplateLib/access/inc/CxxMirrorToJson.h @@ -1,13 +1,23 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once namespace rtl { - namespace access { - class CxxMirror; - } + class CxxMirror; struct CxxMirrorToJson { - static void dump(access::CxxMirror& pCxxMirror, const std::string& pFilePathStr); + static const std::string toJson(const CxxMirror& pCxxMirror); + static void dump(const CxxMirror& pCxxMirror, const std::string& pFilePathStr); }; } \ No newline at end of file diff --git a/ReflectionTemplateLib/access/inc/Function.h b/ReflectionTemplateLib/access/inc/Function.h index 6309c737..42d0bd4a 100644 --- a/ReflectionTemplateLib/access/inc/Function.h +++ b/ReflectionTemplateLib/access/inc/Function.h @@ -1,84 +1,107 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include #include #include -#include "RStatus.h" #include "FunctorId.h" #include "Constants.h" +#include "FunctionCaller.h" namespace rtl { namespace detail { - //forward decls + //forward decls class CxxReflection; - class ReflectionBuilder; - } + class ReflectionBuilder; + } - namespace access - { - /* @class: Function, (callable object) - * every functor (function/method pointer), constructor, destructor registered will produce a 'Function' object - * it contains the meta-data of the functor along with 'FunctorId' to lookup for the same in functor-table. - * once the Function object is obtained, it can be called with the correct set of arguments, which will finally - perform call on the functor represented by this object. - */ class Function - { - //TypeQ::Const/Mute represents the const/non-const member-function, Type::None for non-member functions. - const TypeQ m_qualifier; +/* @class: Function, (callable object) + * every functor (function/method pointer), constructor registered will produce a 'Function' object + * it contains the meta-data of the functor along with 'FunctorId' to lookup for the same in functor-table. + * once the Function object is obtained, it can be called with the correct set of arguments, which will finally + * perform call on the functor represented by this object. +*/ class Function + { + //methodQ::Const/Mute represents the const/non-const member-function, Type::None for non-member & static-member functions. + detail::methodQ m_qualifier; - //type id of class/struct (if it represents a member-function, else always '0') - const std::size_t m_recordTypeId; + //type id of class/struct (if it represents a member-function, else always '0') + std::size_t m_recordTypeId; - //name of the class/struct it belongs to, empty for non-member function. - const std::string m_record; + //name of the class/struct it belongs to, empty for non-member function. + std::string m_record; - //name of the function as supplied by the user. - const std::string m_function; + //name of the function as supplied by the user. + std::string m_function; - //name of the namespace as supplied by the user. - const std::string m_namespace; + //name of the namespace as supplied by the user. + std::string m_namespace; - //FunctorId acts as a hash-key to look up the functor in table. multiple 'FunctoreId' for overloaded functors. - mutable std::vector m_functorIds; + //FunctorId acts as a hash-key to look up the functor in table. multiple 'FunctoreId' for overloaded functors. + mutable std::vector m_functorIds; - Function(const std::string& pNamespace, const std::string& pClassName, - const std::string& pFuncName, const detail::FunctorId& pFunctorId, - const std::size_t pRecordTypeId, const TypeQ pQualifier); + private: - void addOverload(const Function& pOtherFunc) const; + Function(const std::string_view pNamespace, const std::string_view pClassName, + const std::string_view pFuncName, const detail::FunctorId& pFunctorId, + const std::size_t pRecordTypeId, const detail::methodQ pQualifier); - GETTER_REF(std::vector, FunctorIds, m_functorIds) + void addOverload(const Function& pOtherFunc) const; - protected: + protected: - Function(const Function& pOther, const detail::FunctorId& pFunctorId, - const std::string& pFunctorName); + Function(const Function& pOther, const detail::FunctorId& pFunctorId, + const std::string_view pFunctorName); - const std::size_t hasSignatureId(const std::size_t& pSignatureId) const; + const std::size_t hasSignatureId(const std::size_t pSignatureId) const; - public: + GETTER(detail::methodQ, Qualifier, m_qualifier); - //simple inlined getters. - GETTER(TypeQ, Qualifier, m_qualifier) - GETTER(std::string, RecordName, m_record) - GETTER(std::string, Namespace, m_namespace) - GETTER(std::string, FunctionName, m_function) - GETTER(std::size_t, RecordTypeId, m_recordTypeId) - GETTER(std::vector, Functors, m_functorIds) + GETTER_REF(std::vector, FunctorIds, m_functorIds) - template - const bool hasSignature() const; + public: - template - RStatus operator()(_args...params) const noexcept; + //simple inlined getters. + GETTER(std::string, RecordName, m_record); + GETTER(std::string, Namespace, m_namespace); + GETTER(std::string, FunctionName, m_function); + GETTER(std::size_t, RecordTypeId, m_recordTypeId); + GETTER(std::vector, Functors, m_functorIds); - template - RStatus call(_args...params) const noexcept; + Function() = default; + Function(Function&&) = default; + Function(const Function&) = default; + Function& operator=(Function&&) = default; + Function& operator=(const Function&) = default; - friend detail::CxxReflection; - friend detail::ReflectionBuilder; - }; - } + //indicates if a functor associated with it takes zero arguments. + bool hasSignature() const; + + template + bool hasSignature() const; + + template + Return operator()(_args&&...params) const noexcept; + + template + const detail::FunctionCaller<_signature...> bind() const noexcept; + + friend detail::CxxReflection; + friend detail::ReflectionBuilder; + + template + friend class detail::FunctionCaller; + }; } \ No newline at end of file diff --git a/ReflectionTemplateLib/access/inc/Function.hpp b/ReflectionTemplateLib/access/inc/Function.hpp index 9bf7f023..4c620c71 100644 --- a/ReflectionTemplateLib/access/inc/Function.hpp +++ b/ReflectionTemplateLib/access/inc/Function.hpp @@ -1,71 +1,66 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once -#include "RStatus.h" #include "Function.h" -#include "Instance.h" -#include "FunctorContainer.h" +#include "FunctionCaller.hpp" -namespace rtl { - - namespace access +namespace rtl +{ + template + inline const detail::FunctionCaller<_signature...> Function::bind() const noexcept { - /* @method: hasSignature<...>() - @param: set of arguments, explicitly specified as template parameter. - @return: bool, if the functor associated with this object is of certain signature or not. - * a single 'Function' object can be associated with multiple overloads of same function. - * the set of arguments passed is checked agains all registered overloads, returns true if matched with any one. - */ template - inline const bool Function::hasSignature() const - { - //hasSignatureId() returns the index of the 'lambda' in functor-container, which cannot be '-1'. - return (hasSignatureId(detail::FunctorContainer<_arg0, _args...>::getContainerId()) != -1); - } - + return detail::FunctionCaller<_signature...>{ this }; + } - /* @method: hasSignature() - @param: set of arguments, explicitly specified as template parameter. - @return: bool, if the functor associated with this object doesn't takes any argument. - */ template<> - inline const bool Function::hasSignature() const - { - //hasSignatureId() returns the index of 'lambda' in functor-container, which cannot be '-1'. - return (hasSignatureId(detail::FunctorContainer<>::getContainerId()) != -1); - } +/* @method: hasSignature<...>() + @param: set of arguments, explicitly specified as template parameter. + @return: bool, if the functor associated with this object is of certain signature or not. + * a single 'Function' object can be associated with multiple overloads of same function. + * the set of arguments passed is checked agains all registered overloads, returns true if matched with any one. +*/ template + inline bool Function::hasSignature() const + { + //hasSignatureId() returns the index of the 'lambda' in functor-container, which cannot be '-1'. + return (hasSignatureId(detail::FunctorContainer<_args...>::getContainerId()) != -1); + } - /* @method: operator()() - @param: variadic arguments. - @return: RStatus, containing the call status & return value of from the reflected call. - * if the arguments did not match with any overload, returns RStatus with Error::SignatureMismatch - * providing optional syntax, Function::call() does the exact same thing. - */ template - inline RStatus Function::operator()(_args ...params) const noexcept - { - const std::size_t& index = hasSignatureId(detail::FunctorContainer<_args...>::getContainerId()); - if (index != -1) //true, if the arguments sent matches the functor signature associated with this 'Function' object - { - return detail::FunctorContainer<_args...>::forwardCall(index, params...); - } - //else return with Error::SignatureMismatch. - return RStatus(Error::SignatureMismatch); - } +/* @method: operator()() + @param: variadic arguments. + @return: Return, possible error & return value of from the reflected call. + * if the arguments did not match with any overload, returns RObject with error::SignatureMismatch + * providing optional syntax, Function::call() does the exact same thing. +*/ template + inline Return Function::operator()(_args&& ...params) const noexcept + { + return detail::FunctionCaller<>{ this }.call(std::forward<_args>(params)...); + } - /* @method: call() - @param: variadic arguments. - @return: RStatus, containing the call status & return value of from the reflected call. - * if the arguments did not match with any overload, returns RStatus with Error::SignatureMismatch. - * providing optional syntax, Function::operator()() does the exact same thing. - */ template - inline RStatus Function::call(_args ...params) const noexcept - { - const std::size_t& index = hasSignatureId(detail::FunctorContainer<_args...>::getContainerId()); - if (index != -1) //true, if the arguments sent matches the functor signature associated with this 'Function' object - { - return detail::FunctorContainer<_args...>::forwardCall(index, params...); - } - //else return with Error::SignatureMismatch. - return RStatus(Error::SignatureMismatch); - } - } -} \ No newline at end of file +/* @method: hasSignatureId() + @param: const std::size_t& (signatureId to be found) + @return: the index of the functor in the functor-table. + * a 'Function' object may be associated with multiple functors in case of overloads. + * every overload will have unique 'FunctorId', contained by one 'Function' object. + * given signatureId is compared against the signatureId of all overloads registered. +*/ FORCE_INLINE const std::size_t Function::hasSignatureId(const std::size_t pSignatureId) const + { + //simple linear-search, efficient for small set of elements. + for (const auto& functorId : m_functorIds) { + if (functorId.getSignatureId() == pSignatureId) [[likely]] { + return functorId.getIndex(); + } + } + return rtl::index_none; + } +} diff --git a/ReflectionTemplateLib/access/inc/Instance.h b/ReflectionTemplateLib/access/inc/Instance.h deleted file mode 100644 index 387b4a6a..00000000 --- a/ReflectionTemplateLib/access/inc/Instance.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "TypeId.h" -#include "Constants.h" - -namespace rtl { - - namespace access - { - //forward decls - class Record; - class RStatus; - class Function; - - /* @class: Instance - * type erased wrapper for objects created on heap via reflection. - * 'Instance' objects are only returned from Record::clone() & Recoed::instance() - * empty 'Instance' is returned if constructor is not found or if called with wrong args. - * 'Instance' objects are never created on heap, only the underlying object is created on heap. - * the lifetime of the underlying object is managed by std::shared_ptr. - */ class Instance - { - //indicates if object const/non-const. - mutable TypeQ m_qualifier; - - //type id of the containd object. - const std::size_t m_typeId; - - //allocated object, stored without type info. - const std::any m_anyObject; - - /* shared_ptr, wil be shared between the copies of the 'Instance'. - does not holds the objcet constructed via reflection. - it only contains a custom deleter to be called on the underlying object. - */ const std::shared_ptr m_destructor; - - //private constructors, only class 'Record' can access. - explicit Instance(); - explicit Instance(const std::any& pRetObj, const RStatus& pStatus, const Function& pDctor); - - public: - - //creating copies is allowed. - Instance(const Instance&); - - //simple inlined getters. - GETTER(std::any, , m_anyObject); - GETTER(std::size_t, TypeId, m_typeId); - GETTER(TypeQ, Qualifier, m_qualifier); - - //checks if it contains object constructed via reflection. - const bool isEmpty() const; - - //check the contained object is const or not. - const bool isConst() const; - - //treat the object constructed via reflection as const or non-const. - void makeConst(const bool& pCastAway = false); - - //get the current number of objects constructed via reflection. - static std::size_t getInstanceCount(); - - friend Record; - }; - } -} \ No newline at end of file diff --git a/ReflectionTemplateLib/access/inc/Method.h b/ReflectionTemplateLib/access/inc/Method.h index f3610c6e..a57bcbf5 100644 --- a/ReflectionTemplateLib/access/inc/Method.h +++ b/ReflectionTemplateLib/access/inc/Method.h @@ -1,173 +1,105 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include +#include "RObject.h" #include "Function.h" -#include "Instance.h" +#include "MethodInvoker.h" namespace rtl { - namespace access + class Record; + +/* @class: Method + * extends 'Function' class and adds interfaces to call member function. + * invokes only static & non-static member functions via reflection. + * deletes the base's 'operator()()'. + * redefines 'operator()()', to accept only target object and returns lambda. + * the returned lambda is then called with the arguments corresponding to the functor associated with it. +*/ class Method : public Function { - //forward decls - class Method; - class Record; - - /* @class: MethodInvoker - @param: , can be any 'FunctorType' other than FunctorType::Static. - * invokes the assigned method on the assigned object. - * invokes only non-static member function via reflection. - * its objects are only cretaed and returned by 'Method::on()' method. - * purpose of this class is only to provide method call syntax like, 'method.on(target).call(params...)' - */ template - class MethodInvoker - { - //the method to be called. - const Method& m_method; + //private ctor, called by 'Record' class. + Method(const Function& pFunction) + : Function(pFunction) + { } - //the object on which, the method needs to be called. - const Instance& m_target; - - MethodInvoker(const Method& pMethod, const Instance& pTarget); + //private ctor, called by 'Record' class. + Method(const Function& pFunction, const detail::FunctorId& pFunctorId, const std::string& pFunctorName) + : Function(pFunction, pFunctorId, pFunctorName) + { } - public: + //invokes the constructor associated with this 'Method' + template + Return invokeCtor(alloc pAllocType, std::size_t pClonerIndex, _args&&...params) const; - template - RStatus call(_args...) const noexcept; + public: - friend Method; - }; + Method() = default; + Method(Method&&) = default; + Method(const Method&) = default; + Method& operator=(Method&&) = default; + Method& operator=(const Method&) = default; + using Function::bind; - /* @class: MethodInvoker - @param: FunctorType::Static (explicitly specialized) - * invokes the assigned method on the assigned object. - * invokes only static member function via reflection. - * its objects are only cretaed and returned by 'Method::on()' method. - * purpose of this class is only to provide method call syntax like, 'method.on().call(params...)' - * 'on()' will take no target as parameter, since the method being called is 'static'. - */ template<> - class MethodInvoker - { - const Method& m_method; + GETTER_BOOL(Const, (getQualifier() == detail::methodQ::Const)); - MethodInvoker(const Method& pMethod); + //indicates if a particular set of arguments accepted by the functor associated with it. + template + bool hasSignature() const; - public: + template + const detail::DefaultInvoker<_signature...> bind(const RObject& pTarget) const; - template - RStatus call(_args...) const noexcept; + template + const detail::NonConstInvoker<_signature...> bind(constCast&& pTarget) const; - friend Method; - }; + /* @method: operator()() + @return: lambda + * accepts no arguments for 'target', since associated functor is static-member-functions. + * returns a lambda, which forwards the call to finally call the associated static-member-function functor. + * provides syntax like,'method()(params...)', first'()' is empty & second'()' takes the actual params. + */ constexpr auto operator()() const + { + return detail::FunctionCaller<>{ this }; + } - /* @class: Method - * extends 'Function' class and adds interfaces to call member function. - * invokes only static & non-static member functions via reflection. - * deletes the base's 'operator()()'. - * redefines 'operator()()', to accept only target object and returns lambda. - * the returned lambda is then called with the arguments corresponding to the functor associated with it. - */ class Method : public Function + /* @method: operator()(const RObject&) + @param: const RObject& (target object) + @return: lambda + * accepts 'pTarget', which contains the actual object on which the member-function functor associated with 'this' is invoked. + * returns a lambda, which forwards the call to 'call', finally invoking the associated non-static-member-function functor. + * provides syntax like, 'method(pTarget)(params...)', keeping the target & params seperate. + */ constexpr detail::DefaultInvoker<> operator()(const RObject& pTarget) const { - //private ctor, called by 'Record' class. - explicit Method(const Function& pFunction); - - //private ctor, called by 'Record' class. - explicit Method(const Function& pFunction, const detail::FunctorId& pFunctorId, const std::string& pFunctorName); - - //invokes the constructor associated with this 'Method' - template - RStatus invokeCtor(_args...params) const; - - //invokes the member-function associated with this 'Method' - template - RStatus invoke(const Instance& pTarget, _args...params) const; - - //invokes only const member-function associated with this 'Method' - template - RStatus invokeConst(const Instance& pTarget, _args...params) const; - - //invokes only static member-function associated with this 'Method' - template - RStatus invokeStatic(_args...params) const; - - //called from class 'Record', creates a 'Method' object for destructor. - static Method getDestructorMethod(const Function& pFunction, const detail::FunctorId& pFunctorId); - - public: - - //indicates if a particular set of arguments accepted by the functor associated with it. - template - const bool hasSignature() const; - - //set 'no' object to call static method. (takes no parameter) - const MethodInvoker on() const; - - //set 'target' object on which the functor associated with this will be called. - const MethodInvoker on(const Instance& pTarget) const; - - - /* @method: operator()() - @return: lambda - * accepts no arguments for 'target', since associated functor is static-member-functions. - * returns a lambda, which forwards the call to finally call the associated static-member-function functor. - * provides syntax like,'method()(params...)', first'()' is empty & second'()' takes the actual params. - */ constexpr auto operator()() const - { - return [this](auto...params) { - return Function::operator()(params...); - }; - } - - - /* @method: operator()(const Instance&) - @param: const Instance& (target object) - @return: lambda - * accepts 'pTarget', which contains the actual object on which the member-function functor associated with 'this' is invoked. - * returns a lambda, which forwards the call to finally call the associated non-static-member-function functor. - * provides syntax like, 'method(pTarget)(params...)', keeping the target & params seperate. - */ constexpr auto operator()(const Instance& pTarget) const - { - return [&](auto...params)->RStatus - { - if (pTarget.isEmpty()) { - //if the target is empty. - return RStatus(Error::EmptyInstance); - } - - if (pTarget.getTypeId() != getRecordTypeId()) { - //if the target type-id & type-id of the 'class/struct' owner of the associated functor do not match. - return RStatus(Error::InstanceTypeMismatch); - } - - switch (pTarget.getQualifier()) - { - //if the target is non-const, const & non-const member function can be invoked on it. - case TypeQ::Mute: return invoke(pTarget, params...); - - //if the target is const, only const member function can be invoked on it. - case TypeQ::Const: return invokeConst(pTarget, params...); - } - - //only an empty 'Instance' will have TypeQ::None. - return RStatus(Error::EmptyInstance); - }; - } - - //deletes base class 'operator()()' - template - RStatus operator()(_args...) const noexcept = delete; - - //deletes base class 'call()' - template - RStatus call(_args...) const noexcept = delete; - - //friends :) - template - friend class MethodInvoker; - friend detail::CxxReflection; - friend Record; - }; - } + return detail::DefaultInvoker<>{ this, &pTarget }; + } + + constexpr detail::NonConstInvoker<> operator()(constCast&& pTarget) const + { + return detail::NonConstInvoker<>{ this, &pTarget.m_target }; + } + + //friends :) + friend Record; + friend detail::CxxReflection; + + template + friend struct detail::DefaultInvoker; + + template + friend struct detail::NonConstInvoker; + }; } \ No newline at end of file diff --git a/ReflectionTemplateLib/access/inc/Method.hpp b/ReflectionTemplateLib/access/inc/Method.hpp index 40ea066e..eed7f822 100644 --- a/ReflectionTemplateLib/access/inc/Method.hpp +++ b/ReflectionTemplateLib/access/inc/Method.hpp @@ -1,184 +1,72 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include "Method.h" +#include "MethodInvoker.hpp" -namespace rtl +namespace rtl { - namespace access - { - //MethodInvoker, holds const-ref of the 'Method' and 'Instance' on which it will be invoked. - template - inline MethodInvoker<_type>::MethodInvoker(const Method& pMethod, const Instance& pTarget) - : m_method(pMethod) - , m_target(pTarget) { - } - - - /* @method: call() - @params: params... (corresponding to functor associated with 'm_method') - @return: RStatus, indicating success of the reflected call. - * invokes non-static-member-function functor associated with 'm_method' on object 'm_target'. - */ template - template - inline RStatus MethodInvoker<_type>::call(_args ...params) const noexcept - { - if (m_target.isEmpty()) { - //if the target is empty. - return RStatus(Error::EmptyInstance); - } - - if (m_target.getTypeId() != m_method.getRecordTypeId()) { - //if the m_target's type-id & type-id of the 'class/struct' owner of the associated functor(m_method's) do not match. - return RStatus(Error::InstanceTypeMismatch); - } - - switch (m_target.getQualifier()) - { - //if the target is non-const, const & non-const both type member-function can be invoked on it. - case TypeQ::Mute: return m_method.invoke(m_target, params...); - - //if the m_target is const, only const member function can be invoked on it. - case TypeQ::Const: return m_method.invokeConst(m_target, params...); - } - - //only an empty 'Instance' will have TypeQ::None. - return RStatus(Error::EmptyInstance); - } - - //MethodInvoker, holds only 'Method' associated with a static-member-function. - inline MethodInvoker::MethodInvoker(const Method& pMethod) - :m_method(pMethod) { - } - - - template - inline RStatus MethodInvoker::call(_args ...params) const noexcept - { - //invokes the static-member-function functor associated with 'm_method'. no need of 'm_target' as other 'MethodInvoker'. - return m_method.invokeStatic(params...); - } + template + FORCE_INLINE const detail::DefaultInvoker<_signature...> Method::bind(const RObject& pTarget) const + { + return detail::DefaultInvoker<_signature...>{ this, &pTarget }; } - namespace access + template + FORCE_INLINE const detail::NonConstInvoker<_signature...> Method::bind(constCast&& pTarget) const { - /* @method: on() - @return: MethodInvoker - * accepts no arguments for 'target', since associated functor is static-member-functions. - */ inline const MethodInvoker Method::on() const - { - return MethodInvoker(*this); - } - - - /* @method: on() - @return: MethodInvoker - * accepts 'pTarget', which contains the actual object on which the member-function functor associated with 'this' is invoked. - */ inline const MethodInvoker Method::on(const Instance& pTarget) const - { - return MethodInvoker(*this, pTarget); - } - - - /* @method: invokeCtor() - @params: variable arguments. - @return: RStatus - * calls the constructor with given arguments. - */ template - inline RStatus Method::invokeCtor(_args ...params) const - { - return Function::operator()(params...); - } - - - /* @method: invokeStatic() - @params: variable arguments. - @return: RStatus - * with given arguments, calls the static-member-function functor associated with this 'Method'. - */ template - inline RStatus Method::invokeStatic(_args ...params) const - { - return Function::operator()(params...); - } + return detail::NonConstInvoker<_signature...>{ this, &pTarget.m_target }; + } - /* @method: hasSignature() - @return: bool - * checks if the member-function functor associated with this 'Method', takes zero arguments or not. - */ template<> - inline const bool Method::hasSignature() const - { - switch (getQualifier()) - { - case TypeQ::None: return Function::hasSignature(); - case TypeQ::Mute: return (hasSignatureId(detail::MethodContainer::getContainerId()) != -1); - case TypeQ::Const: return (hasSignatureId(detail::MethodContainer::getContainerId()) != -1); - } - return false; - } - +/* @method: invokeCtor() + @params: variable arguments. + @return: RStatus + * calls the constructor with given arguments. +*/ template + inline Return Method::invokeCtor(alloc pAllocType, std::size_t pClonerIndex, _args&& ...params) const + { + using Container = detail::FunctorContainer...>; - /* @method: hasSignature<...>() - @params: template params, <_arg0, ..._args> (expects at least one args- _args0) - @return: bool - * checks if the member-function functor associated with this 'Method', takes template specified arguments set or not. - */ template - inline const bool Method::hasSignature() const - { - switch (getQualifier()) - { - case TypeQ::None: return Function::hasSignature<_arg0, _args...>(); - case TypeQ::Mute: return (hasSignatureId(detail::MethodContainer::getContainerId()) != -1); - case TypeQ::Const: return (hasSignatureId(detail::MethodContainer::getContainerId()) != -1); - } - return false; + std::size_t index = hasSignatureId(Container::getContainerId()); + if (index != rtl::index_none) [[likely]] { + return Container::template forwardCall<_args...>(index, pAllocType, pClonerIndex, std::forward<_args>(params)...); } + return { error::SignatureMismatch, RObject{} }; + } - /* @method: invokeConst() - @params: 'pTarget' (on which the method to be invoked), 'params...' (method arguments) - @return: 'RStatus', indicating the success of reflected method call. - * invokes only a const-member-function functor. - */ template - inline RStatus Method::invokeConst(const Instance& pTarget, _args ...params) const +/* @method: hasSignature<...>() + @params: template params, <_arg0, ..._args> (expects at least one args- _args0) + @return: bool + * checks if the member-function functor associated with this 'Method', takes template specified arguments set or not. +*/ template + inline bool Method::hasSignature() const + { + switch (getQualifier()) { - //if the given argument's associated MethodContainer contains such member-functor, then make the call. - const std::size_t& index = hasSignatureId(detail::MethodContainer::getContainerId()); - if (index != -1) - { - //make the call. - return detail::MethodContainer::forwardCall(pTarget.get(), index, params...); - } - else { - //if the associated MethodContainer contains no such member-functor, check if such functor is present in container holding non-const functors. - const std::size_t& index = hasSignatureId(detail::MethodContainer::getContainerId()); - if (index != -1) { - //if yes, then return error indicating such 'functor' is present but can be called on only non-const 'Instance'. - return RStatus(Error::InstanceConstMismatch); - } + case detail::methodQ::None: { + return Function::hasSignature<_args...>(); } - //return this error if the given argument's associated MethodContainer not found (const/non-const both). - return RStatus(Error::SignatureMismatch); - } - - - /* @method: invokeConst() - @params: 'pTarget' (on which the method to be invoked), 'params...' (method arguments) - @return: 'RStatus', indicating the success of reflected method call. - * can invoke a 'const' or non-const-member-function functor. - */ template - inline RStatus Method::invoke(const Instance& pTarget, _args ...params) const - { - //if the given argument's associated MethodContainer contains such member-functor, then make the call. - const std::size_t& index = hasSignatureId(detail::MethodContainer::getContainerId()); - if (index != -1) - { - //make the call. - return detail::MethodContainer::forwardCall(pTarget.get(), index, params...); + case detail::methodQ::NonConst: { + using Container = detail::MethodContainer; + return (hasSignatureId(Container::getContainerId()) != -1); } - else { - //if no such member-functor is found in non-const MethodContainer, check if such functor is present in const MethodContainer and call. - return invokeConst(pTarget, params...); + case detail::methodQ::Const: { + using Container = detail::MethodContainer; + return (hasSignatureId(Container::getContainerId()) != -1); } } + return false; } } \ No newline at end of file diff --git a/ReflectionTemplateLib/access/inc/RObject.h b/ReflectionTemplateLib/access/inc/RObject.h new file mode 100644 index 00000000..86ded7c0 --- /dev/null +++ b/ReflectionTemplateLib/access/inc/RObject.h @@ -0,0 +1,114 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include +#include +#include + +#include "view.h" +#include "TypeId.h" +#include "RObjectId.h" +#include "rtl_traits.h" + + +namespace rtl::detail +{ + template + struct RObjectUPtr; + + class RObjExtractor; + + template + struct RObjectBuilder; +} + + +namespace rtl +{ + struct Return; + class Function; + class CxxMirror; + + //Reflecting the object within. + class RObject + { + std::optional m_object = std::nullopt; + detail::RObjectId m_objectId = {}; + const std::vector* m_converters = nullptr; + + RObject(const RObject&) = default; + RObject(std::any&& pObject, detail::RObjectId&& pRObjId, + const std::vector* pConverters) noexcept; + + std::size_t getConverterIndex(const std::size_t pToTypeId) const; + + template + Return createCopy() const; + + template + std::optional> performConversion(const std::size_t pIndex) const; + + public: + + RObject() = default; + ~RObject() = default; + RObject& operator=(const RObject&) = delete; + + RObject(RObject&&) noexcept; + RObject& operator=(RObject&&) noexcept; + + GETTER_BOOL(Empty, (m_object == std::nullopt)) + GETTER_BOOL(OnHeap, (m_objectId.m_allocatedOn == alloc::Heap)) + GETTER_BOOL(AllocatedByRtl, (m_objectId.m_allocatedOn == alloc::Heap)) + GETTER(std::size_t, TypeId, m_objectId.m_typeId) + GETTER_CREF(std::optional, Any, m_object) + + /* Reflection Const Semantics: + * - All reflected objects default to mutable internally; API enforces logical constness. + * - RTL may 'const_cast' its own objects(allocated via RTL) but preserves logical constness. + * - External objects (e.g. returned via Reflected call) keep original qualifier; if const, then const_cast is unsafe. + */ GETTER_BOOL(ConstCastSafe, m_objectId.m_isConstCastSafe) + + template + bool canViewAs() const; + + template + Return clone() const; + + template, int> = 0> + std::optional> view() const noexcept; + + template, int> = 0> + std::optional> view() const noexcept; + + template, int> = 0> + std::optional> view() const noexcept; + + static std::atomic& getInstanceCounter(); + + //friends :) + friend CxxMirror; + friend detail::RObjExtractor; + + template + friend struct detail::RObjectUPtr; + + template + friend struct detail::RObjectBuilder; + }; + + struct [[nodiscard]] Return { + error err; + RObject rObject; + }; +} \ No newline at end of file diff --git a/ReflectionTemplateLib/access/inc/RObject.hpp b/ReflectionTemplateLib/access/inc/RObject.hpp new file mode 100644 index 00000000..792dfcfd --- /dev/null +++ b/ReflectionTemplateLib/access/inc/RObject.hpp @@ -0,0 +1,267 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include +#include +#include + +#include "view.hpp" +#include "RObject.h" +#include "RObjectUPtr.h" +#include "ReflectCast.h" +#include "RObjExtracter.h" +#include "RObjectBuilder.h" +#include "FunctorContainer.h" + +namespace rtl +{ + FORCE_INLINE RObject::RObject(std::any&& pObject, detail::RObjectId&& pRObjId, + const std::vector* pConverters) noexcept + : m_object(std::in_place, std::move(pObject)) + , m_objectId(pRObjId) + , m_converters(pConverters) + { } + + inline RObject::RObject(RObject&& pOther) noexcept + : m_object(std::move(pOther.m_object)) + , m_objectId(pOther.m_objectId) + , m_converters(pOther.m_converters) + { + // Explicitly clear moved-from source + pOther.m_object = std::nullopt; + pOther.m_objectId = {}; + pOther.m_converters = nullptr; + } + + inline RObject& RObject::operator=(RObject&& pOther) noexcept + { + if (this == &pOther) { + return *this; + } + + m_object = std::move(pOther.m_object); + m_objectId = pOther.m_objectId; + m_converters = pOther.m_converters; + + // Explicitly clear moved-from source + pOther.m_object = std::nullopt; + pOther.m_objectId = {}; + pOther.m_converters = nullptr; + return *this; + } + + inline std::atomic& RObject::getInstanceCounter() + { + static std::atomic instanceCounter = {0}; + return instanceCounter; + } + + + inline std::size_t RObject::getConverterIndex(const std::size_t pToTypeId) const + { + if (m_objectId.m_containsAs != detail::EntityKind::None) { + for (std::size_t index = 0; index < m_converters->size(); index++) { + if ((*m_converters)[index].first == pToTypeId) { + return index; + } + } + } + return index_none; + } + + + template + inline bool RObject::canViewAs() const + { + if (isEmpty()) { + return false; + } + + if constexpr (traits::is_bare_type()) { + if constexpr (traits::std_wrapper::type != detail::Wrapper::None) { + if (m_objectId.m_wrapperTypeId == traits::std_wrapper::id()) { + return true; + } + } + const auto& typeId = detail::TypeId::get(); + return (m_objectId.m_typeId == typeId || getConverterIndex(typeId) != index_none); + } + } + + + template + inline std::optional> RObject::performConversion(const std::size_t pIndex) const + { + detail::EntityKind newKind = detail::EntityKind::None; + const traits::Converter& convert = (*m_converters)[pIndex].second; + const std::any& viewObj = convert(m_object.value(), m_objectId.m_containsAs, newKind); + const T* viewRef = detail::RObjExtractor::getPointer(viewObj, newKind); + + if (viewRef != nullptr && newKind == detail::EntityKind::Ptr) { + return std::optional>(std::in_place, *viewRef); + } + else if (viewRef != nullptr && newKind == detail::EntityKind::Value) { + if constexpr (std::is_copy_constructible_v) { + return std::optional>(std::in_place, T(*viewRef)); + } + } + return std::nullopt; + } + + + template , int>> + FORCE_INLINE std::optional> RObject::view() const noexcept + { + if (isEmpty()) { + return std::nullopt; + } + + if constexpr (traits::is_bare_type()) + { + if (detail::TypeId::get() == m_objectId.m_wrapperTypeId) [[likely]] + { + using U = detail::RObjectUPtr::value_type>; + const U& uptrRef = *(detail::RObjExtractor{ this }.getWrapper()); + return std::optional>(std::in_place, static_cast(uptrRef)); + } + } + return std::nullopt; + } + + + template , int>> + FORCE_INLINE std::optional> RObject::view() const noexcept + { + if (isEmpty()) { + return std::nullopt; + } + + if constexpr (traits::is_bare_type()) + { + if (detail::TypeId::get() == m_objectId.m_wrapperTypeId) [[likely]] + { + const T* sptrRef = detail::RObjExtractor{ this }.getWrapper(); + if (sptrRef != nullptr) { + return std::optional>(std::in_place, const_cast(*sptrRef)); + } + } + } + return std::nullopt; + } + + + template , int>> + FORCE_INLINE std::optional> RObject::view() const noexcept + { + if (isEmpty()) { + return std::nullopt; + } + + if constexpr (traits::is_bare_type()) + { + const std::size_t asTypeId = detail::TypeId::get(); + if (asTypeId == m_objectId.m_typeId) [[likely]] + { + const T* valRef = detail::RObjExtractor{ this }.getPointer(); + if (valRef != nullptr) { + return std::optional>(std::in_place, *valRef); + } + } + else + { + const std::size_t index = getConverterIndex(asTypeId); + if (index != index_none) { + return performConversion(index); + } + } + } + return std::nullopt; + } +} + + + +namespace rtl +{ + template<> + inline Return RObject::createCopy() const + { + std::size_t pClonerIndex = m_objectId.m_clonerIndex; + if (pClonerIndex != rtl::index_none) + { + return traits::Cloner::template forwardCall(pClonerIndex, alloc::Heap, pClonerIndex, *this); + } + return { error::CloningDisabled, RObject{} }; + } + + + template<> + inline Return RObject::createCopy() const + { + std::size_t pClonerIndex = m_objectId.m_clonerIndex; + if (pClonerIndex != rtl::index_none) + { + return traits::Cloner::template forwardCall(pClonerIndex, alloc::Stack, pClonerIndex, *this); + } + return { error::CloningDisabled, RObject{} }; + } + + + template<> + inline Return RObject::createCopy() const + { + return { error::StlWrapperHeapAllocForbidden, RObject{} }; + } + + + template<> + inline Return RObject::createCopy() const + { + if (m_objectId.m_wrapperType == detail::Wrapper::None) { + return { error::NotWrapperType, RObject{} }; + } + else if (m_objectId.m_wrapperType == detail::Wrapper::Unique) + { + return { error::TypeNotCopyConstructible, RObject{} }; + } + else { + return { error::None, RObject(*this) }; + } + } + + + template + inline Return RObject::clone() const + { + if (isEmpty()) { + return { error::EmptyRObject, RObject{} }; + } + if constexpr (_copyTarget == copy::Value) { + return createCopy<_allocOn, detail::EntityKind::Value>(); + } + else if constexpr (_copyTarget == copy::Wrapper) { + return createCopy<_allocOn, detail::EntityKind::Wrapper>(); + } + else if constexpr (_copyTarget == copy::Auto) { + // RTL wraps the objects allocated on heap in 'std::unique_ptr'. Which by default is transparent to RTL itself. + // 'std::unique_ptr' acquired via any other source, (e.g. return value) are not transparent. hence the second condition. + if (m_objectId.m_wrapperType != detail::Wrapper::None && !isAllocatedByRtl()) + { + return createCopy<_allocOn, detail::EntityKind::Wrapper>(); + } + else { + return createCopy<_allocOn, detail::EntityKind::Value>(); + } + } + } +} diff --git a/ReflectionTemplateLib/access/inc/RStatus.h b/ReflectionTemplateLib/access/inc/RStatus.h deleted file mode 100644 index d61d8be9..00000000 --- a/ReflectionTemplateLib/access/inc/RStatus.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include -#include "Constants.h" -#include "TypeId.h" - -namespace rtl -{ - namespace access - { - /* @class: RStatus - * Every reflection call made, returns a RStatus object. - * it contains the error status of the call, defined by enum rtl::Error (in Constants.h) - * indicates all possible failure-errors that could happen on calling reflected funtion/method/constructor. - * it also contains the return value/object from the reflected function/method call wrapped under std::any. - */ class RStatus - { - //indicates the reflection call status error - const Error m_callStatus; - - //indicates whether the returned value from reflected call is const/non-const. - const TypeQ m_typeQualifier; - - //contains the return value of the from reflected call. Type erased. - const std::any m_returnObj; - - //type-id of the return value. - const std::size_t m_typeId; - - public: - - //used when the reflected call doesn't have any return value, or in case of call failure. - RStatus(const Error pCallStatus); - - //used when the reflected call returns a value, called only in case of no call failure. - RStatus(const std::any& pRetObj, const std::size_t pTypeId, const TypeQ pQualifier); - - GETTER(std::any, Return, m_returnObj) - GETTER(std::size_t, TypeId, m_typeId) - GETTER(TypeQ, Qualifier, m_typeQualifier) - - //RStatus object converted to bool based on call succes or not. - operator bool() const { - //Error::None, reflected call successful. - return (m_callStatus == Error::None); - } - - //RStatus object can be directly checked agains any error-code. - const bool operator==(const Error pError) const { - return (m_callStatus == pError); - } - - //check if the returned object is of certain type. expected type must be passed as template param. - //if the expected type is 'const', must be used as templeate parameter. - template - constexpr const bool isOfType() const { - return (detail::TypeId<_type>::get() == m_typeId); - } - }; - } -} \ No newline at end of file diff --git a/ReflectionTemplateLib/access/inc/Record.h b/ReflectionTemplateLib/access/inc/Record.h index 2ae78ae4..6a608de8 100644 --- a/ReflectionTemplateLib/access/inc/Record.h +++ b/ReflectionTemplateLib/access/inc/Record.h @@ -1,3 +1,14 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include @@ -5,53 +16,89 @@ #include #include "Method.h" +#include "Constants.h" + +namespace rtl::detail +{ + //forward decl. + class CxxReflection; +} namespace rtl { - //forward decls - namespace detail { - class CxxReflection; - } + //forward decls + class Method; + class RObject; - namespace access +/* @class: Record + * represents a reflected class/struct. + * contains registered member-functions as 'Method' objects. + * provides interface to access methods by name. + * provides interface to construct instances of the class/struct using the registered constructors. +*/ class Record { - //forward decls - class Method; - class RStatus; - class Instance; - - /* @class: Record - * represents a reflected class/struct. - * contains registered member-functions as 'Method' objects. - * provides interface to access methods by name. - * provides interface to construct instances of the class/struct using the registered constructors. - */ class Record - { - const std::string m_recordName; - - mutable std::unordered_map< std::string, access::Method > m_methods; - - Record(const std::string& pRecordName); - - std::unordered_map< std::string, access::Method >& getFunctionsMap() const; + using MethodMap = std::unordered_map< std::string, Method >; - public: + mutable std::size_t m_recordId; + mutable std::string m_namespace; + mutable std::string m_recordName; + mutable MethodMap m_methods; - Record() = delete; + private: - std::optional getMethod(const std::string& pMethod) const; - - //creates dynamic instance, calling copy ctor, using new. - const std::pair clone(Instance& pOther) const; - - //creates dynamic instance, using new. - template - const std::pair instance(_ctorArgs ...params) const; - - const std::unordered_map< std::string, access::Method >& getMethodMap() const; - - //only class which can create objects of this class & manipulates 'm_methods'. - friend class detail::CxxReflection; - }; - } + Record(const std::string& pRecordName, const std::size_t pRecordId, const std::string& pNamespace) + : m_recordId(pRecordId) + , m_namespace(pNamespace) + , m_recordName(pRecordName) + { + } + + GETTER_REF(MethodMap, FunctionsMap, m_methods) + + public: + + Record() = delete; + Record(Record&&) = default; + Record(const Record&) = default; + Record& operator=(Record&&) = default; + Record& operator=(const Record&) = default; + + GETTER_CREF(MethodMap, MethodMap, m_methods) + GETTER_CREF(std::string, RecordName, m_recordName) + +/* @method: getMethod + @param: const std::string& (name of the method) + @return: std::optional + * if the method isn't found by the given name, std::nullopt is returned. +*/ std::optional getMethod(const std::string& pMethod) const + { + const auto& itr = m_methods.find(pMethod); + if (itr != m_methods.end()) { + return std::optional(itr->second); + } + return std::nullopt; + } + + +/* @method: create + @param: ...params (any number/type of arguments) + @return: Return + * calls the constructor of the calss/struct represented by this 'Record' object. + * returns the dynamically allocated object of the calss/struct along with the status. + * only default or any other overloaded constructor is called, except copy (for that check, Record::clone()). + * if the signature(...params) did not match any registered ctor, error::SignatureMismatch is returned with empty 'RObject'. + * if no constructor found, error::ConstructorNotRegisteredInRtl is returned with empty 'RObject'. + * on success error::None and newly constructed object wrapped under 'RObject' (type erased, treated as non-const) is returned. +*/ template + Return create(_ctorArgs&& ...params) const + { + static_assert(_alloc != rtl::alloc::None, "Instance cannot be created with 'rtl::alloc::None' option."); + const auto& method = m_methods.at(detail::ctor_name(m_recordName)); + std::size_t copyCtorIndex = method.getFunctorIds()[detail::Index::CopyCtor].getIndex(); + return method.invokeCtor(_alloc, copyCtorIndex, std::forward<_ctorArgs>(params)...); + } + + //only class which can create objects of this class & manipulates 'm_methods'. + friend class detail::CxxReflection; + }; } \ No newline at end of file diff --git a/ReflectionTemplateLib/access/inc/Record.hpp b/ReflectionTemplateLib/access/inc/Record.hpp deleted file mode 100644 index 42a47fec..00000000 --- a/ReflectionTemplateLib/access/inc/Record.hpp +++ /dev/null @@ -1,52 +0,0 @@ - -#include "Record.h" -#include "RStatus.h" -#include "Method.h" -#include "Constants.h" -#include "Instance.h" - -namespace rtl { - - namespace access - { - /* @method: instance - @param: ...params (any number/type of arguments) - @return: std::pair - * calls the constructor of the calss/struct represented by this 'Record' object. - * returns the dynamically allocated object of the calss/struct along with the status. - * only default or any other overloaded constructor is called, except copy (for that check, Record::clone()). - * if the signature(...params) did not match any registered ctor, Error::SignatureMismatch is returned as RStatus. - * if no constructor found, Error::ConstructorNotFound is returned as RStatus. - * in case of reflected call failure, empty 'Instance' will be returned. - * on success Error::None will be returned along with the newly constructed object wrapped under 'Instance' (type erased). - */ template - inline const std::pair Record::instance(_ctorArgs ...params) const - { - const auto& itr = m_methods.find(CtorName::ctor(m_recordName)); - - //if registered constructor is found for the class/struct represented by this 'Record' object. - if (itr != m_methods.end()) { - - //invoke the constructor, forwarding the arguments. - const RStatus& status = itr->second.invokeCtor(params...); - - //if status is 'true', object construction is successful. - if (status) { - - //get the destructor 'Function', which is gauranteed to be present, if at least one constructor is registered. - const Function dctor = *getMethod(CtorName::dctor(m_recordName)); - - //construct the 'Instance' object, assigning the destructor as custom deleter, its lifetime is managed via std::shared_ptr. - return std::make_pair(status, Instance(status.getReturn(), status, dctor)); - } - //if reflected call fails, return with empty 'Instance'. - return std::make_pair(status, Instance()); - } - else { - - //if no constructor found, return with empty 'Instance'. - return std::make_pair(RStatus(Error::ConstructorNotFound), Instance()); - } - } - } -} \ No newline at end of file diff --git a/ReflectionTemplateLib/access/src/CMakeLists.txt b/ReflectionTemplateLib/access/src/CMakeLists.txt index 3421a930..2615a2f3 100644 --- a/ReflectionTemplateLib/access/src/CMakeLists.txt +++ b/ReflectionTemplateLib/access/src/CMakeLists.txt @@ -3,28 +3,30 @@ set(LOCAL_SOURCES "${CMAKE_CURRENT_LIST_DIR}/CxxMirror.cpp" "${CMAKE_CURRENT_LIST_DIR}/CxxMirrorToJson.cpp" "${CMAKE_CURRENT_LIST_DIR}/Function.cpp" - "${CMAKE_CURRENT_LIST_DIR}/Instance.cpp" - "${CMAKE_CURRENT_LIST_DIR}/Method.cpp" - "${CMAKE_CURRENT_LIST_DIR}/Record.cpp" - "${CMAKE_CURRENT_LIST_DIR}/RStatus.cpp" ) SET(COMMON_HEADERS + + "${PROJECT_SOURCE_DIR}/common/view.h" + "${PROJECT_SOURCE_DIR}/common/view.hpp" "${PROJECT_SOURCE_DIR}/common/Constants.h" + "${PROJECT_SOURCE_DIR}/common/rtl_traits.h" + "${PROJECT_SOURCE_DIR}/common/error_codes.h" + "${PROJECT_SOURCE_DIR}/common/ConversionUtils.h" "${PROJECT_SOURCE_DIR}/common/RTLibInterface.h" ) SET(LOCAL_HEADERS "${PROJECT_SOURCE_DIR}/access/inc/CxxMirror.h" + "${PROJECT_SOURCE_DIR}/access/inc/CxxMirror.hpp" "${PROJECT_SOURCE_DIR}/access/inc/CxxMirrorToJson.h" "${PROJECT_SOURCE_DIR}/access/inc/Function.h" "${PROJECT_SOURCE_DIR}/access/inc/Function.hpp" - "${PROJECT_SOURCE_DIR}/access/inc/Instance.h" "${PROJECT_SOURCE_DIR}/access/inc/Method.h" "${PROJECT_SOURCE_DIR}/access/inc/Method.hpp" "${PROJECT_SOURCE_DIR}/access/inc/Record.h" - "${PROJECT_SOURCE_DIR}/access/inc/Record.hpp" - "${PROJECT_SOURCE_DIR}/access/inc/RStatus.h" + "${PROJECT_SOURCE_DIR}/access/inc/RObject.h" + "${PROJECT_SOURCE_DIR}/access/inc/RObject.hpp" ) # Add any additional source files if needed diff --git a/ReflectionTemplateLib/access/src/CxxMirror.cpp b/ReflectionTemplateLib/access/src/CxxMirror.cpp index 26d9db53..b0167bf2 100644 --- a/ReflectionTemplateLib/access/src/CxxMirror.cpp +++ b/ReflectionTemplateLib/access/src/CxxMirror.cpp @@ -1,89 +1,41 @@ - -#include "Record.h" -#include "Function.h" -#include "Method.h" +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#include "RObject.h" #include "CxxMirror.h" -#include "Constants.h" +#include "ReflectCast.h" -namespace rtl { - - namespace access +namespace rtl +{ + namespace detail { - /* @Constructor: CxxMirror - @params: 'const std::vector&' - * accepts vector of 'Function' objects, which are hash-key to lookup a functor. - * the only constructor to construct 'CxxMirror' object. - * Syntax for constructing - CxxMirror({ Reflect().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& pFunctions) - : detail::CxxReflection(pFunctions) { - } - - - /* @method: getRecord - @param: const std::string& (name of the class/struct) - @return: std::optional - * 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 CxxMirror::getRecord(const std::string& pRecord) const - { - return getRecord(NAMESPACE_GLOBAL, pRecord); - } - - - /* @method: getFunction - @param: const std::string& (name of the non-member function) - @return: std::optional - * 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 CxxMirror::getFunction(const std::string& pFunction) const + std::size_t generate_unique_id() { - return getFunction(NAMESPACE_GLOBAL, pFunction); + // Starts with ONE, ZERO denotes TypeId<>::None. [Never change, critical.] + static std::atomic counter{ TypeId<>::None + 1 }; + return counter.fetch_add(1, std::memory_order_relaxed); } + } - - /* @method: getRecord - @param: std::string (namespace name), std::string (class/struct name) - @return: std::optional - * 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 CxxMirror::getRecord(const std::string& pNameSpace, const std::string& pRecord) const - { - const auto& nsRecordMap = getNamespaceRecordMap(); - const auto& itr = nsRecordMap.find(pNameSpace); - if (itr != nsRecordMap.end()) - { - const auto& recordMap = itr->second; - const auto& itr0 = recordMap.find(pRecord); - 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 - * 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 CxxMirror::getFunction(const std::string& pNameSpace, const std::string& pFunction) const + error CxxMirror::setupCloning(const RObject& pTarget) const + { + const auto& itr = getRecordIdMap().find(pTarget.getTypeId()); + if (itr != getRecordIdMap().end()) { - const auto& nsFunctionMap = getNamespaceFunctionsMap(); - const auto& itr = nsFunctionMap.find(pNameSpace); - if (itr != nsFunctionMap.end()) - { - const auto& functionMap = itr->second; - const auto& itr0 = functionMap.find(pFunction); - if (itr0 != functionMap.end()) { - return std::make_optional(itr0->second); - } - } - return std::nullopt; + const Record& record = itr->second; + Method ctors = record.getMethod(detail::ctor_name(record.getRecordName())).value(); + std::size_t copyCtorIndex = ctors.getFunctors()[detail::Index::CopyCtor].getIndex(); + const_cast(pTarget).m_objectId.m_clonerIndex = copyCtorIndex; + return error::None; } + return error::CloningDisabled; } } \ No newline at end of file diff --git a/ReflectionTemplateLib/access/src/CxxMirrorToJson.cpp b/ReflectionTemplateLib/access/src/CxxMirrorToJson.cpp index cbe2cf73..cf03c065 100644 --- a/ReflectionTemplateLib/access/src/CxxMirrorToJson.cpp +++ b/ReflectionTemplateLib/access/src/CxxMirrorToJson.cpp @@ -1,6 +1,17 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + #include #include +#include #include "Method.h" #include "Record.h" @@ -8,48 +19,72 @@ #include "CxxMirror.h" #include "CxxMirrorToJson.h" -using namespace rtl::access; +using namespace rtl; using namespace rtl::detail; -namespace +static const std::string toJson(const FunctorId& pFunctorId) { - const std::string toJson(const FunctorId& pFunctorId) - { - std::stringstream sout; - sout << "{\"hash_code\": \"" << std::to_string(pFunctorId.getHashCode()) << "\","; - sout << "\"signature\": \"" << pFunctorId.getSignatureStr() << "\"}"; - return sout.str(); + std::stringstream sout; + sout << "{\"containerId\": \"" << std::to_string(pFunctorId.getSignatureId()) << "\","; + sout << "\"index\": \"" << std::to_string(pFunctorId.getIndex()) << "\","; + if (pFunctorId.getRecordId() != TypeId<>::None) { + sout << "\"recordId\": \"" << std::to_string(pFunctorId.getRecordId()) << "\","; } + sout << "\"returnId\": \"" << std::to_string(pFunctorId.getReturnId()) << "\","; + sout << "\"hash_code\": \"" << std::to_string(pFunctorId.getHashCode()) << "\","; + sout << "\"signature\": \"" << pFunctorId.getSignatureStr() << "\"}"; + return sout.str(); +} - const std::string toJson(const Function& pFunction) - { - std::stringstream sout; - const auto& functors = pFunction.getFunctors(); - const std::string& record = pFunction.getRecordName(); - const std::string& nmspace = pFunction.getNamespace(); - sout << "{" << (record.empty() ? "\"function\"" : "\"method\"") << ": \"" << pFunction.getFunctionName() << "\","; - if (nmspace != rtl::NAMESPACE_GLOBAL) { - sout << "\"namespace\": \"" << nmspace << "\","; - } - if (!record.empty()) { - sout << "\"record\": \"" << record << "\","; +static const std::string toJson(const Function& pFunction) +{ + std::stringstream sout; + const auto& functors = pFunction.getFunctors(); + const std::string& record = pFunction.getRecordName(); + const std::string& nmspace = pFunction.getNamespace(); + + sout << "{" << (record.empty() ? "\"function\"" : "\"method\"") << ": \"" << pFunction.getFunctionName() << "\","; + if (nmspace != rtl::detail::NAMESPACE_GLOBAL) { + sout << "\"namespace\": \"" << nmspace << "\","; + } + if (!record.empty()) { + sout << "\"record\": \"" << record << "\","; + } + + int index = 0; + sout << "\"functorId\": ["; + for (const auto& funtorId : functors) { + sout << toJson(funtorId); + if (++index < functors.size()) { + sout << ", "; } + } + sout << "]}"; + return sout.str(); +} - int index = 0; - sout << "\"functorId\": ["; - for (const auto& funtorId : functors) { - sout << toJson(funtorId); - if (++index < functors.size()) { - sout << ", "; - } + +namespace rtl +{ + void CxxMirrorToJson::dump(const CxxMirror& pCxxMirror, const std::string& pFilePathStr) + { + std::string fileStr = pFilePathStr; + std::replace(fileStr.begin(), fileStr.end(), '\\', '/'); + std::fstream fout(fileStr, std::ios::out); + if (!fout.is_open()) { + return; + } + fout << toJson(pCxxMirror); + fout.flush(); + fout.close(); + if (fout.fail() || fout.bad()) { + return; } - sout << "]}"; - return sout.str(); } - const std::string toJson(CxxMirror& pCxxMirror) + const std::string CxxMirrorToJson::toJson(const CxxMirror& pCxxMirror) { std::stringstream sout; sout << "["; @@ -59,7 +94,7 @@ namespace { for (const auto& itr0 : itr.second) { - const std::string& functionStr = toJson(itr0.second); + const std::string& functionStr = ::toJson(itr0.second); sout << functionStr << ","; atLeastOne = true; } @@ -70,9 +105,9 @@ namespace { for (const auto& itr0 : itr.second) { - for (const auto& itr1 : itr0.second.getMethodMap()) + for (const auto& itr1 : itr0.second.get().getMethodMap()) { - const std::string& methodStr = toJson(itr1.second); + const std::string& methodStr = ::toJson(itr1.second); sout << methodStr << ","; atLeastOne = true; } @@ -85,23 +120,3 @@ namespace return str; } } - - -namespace rtl -{ - void CxxMirrorToJson::dump(CxxMirror& pCxxMirror, const std::string& pFilePathStr) - { - std::string fileStr = pFilePathStr; - std::replace(fileStr.begin(), fileStr.end(), '\\', '/'); - std::fstream fout(fileStr, std::ios::out); - if (!fout.is_open()) { - return; - } - fout << toJson(pCxxMirror); - fout.flush(); - fout.close(); - if (fout.fail() || fout.bad()) { - return; - } - } -} \ No newline at end of file diff --git a/ReflectionTemplateLib/access/src/Function.cpp b/ReflectionTemplateLib/access/src/Function.cpp index 0402d4ef..ad1109c9 100644 --- a/ReflectionTemplateLib/access/src/Function.cpp +++ b/ReflectionTemplateLib/access/src/Function.cpp @@ -1,84 +1,79 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ -#include "Function.h" -namespace rtl { +#include - namespace access - { - /* @constructor: Function() - @params: pNamespace - given namespace while registering the type. - * pRecord - given class/struct name, empty if this 'Function' represents a non-member functor - * pFunction - given name of the function as string. - * pFunctorId - 'FunctorId', generated for every functor being registered. - * pRecordTypeId - type id of class/struct if the functor is member-function, '0' for non-member-functions. - * pQualifier - whether the member-function is const or non-const. TypeQ::None for non-member-functions. - * 'Function' object is created for every functor (member/non-member) being registered. - */ Function::Function(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction, const detail::FunctorId& pFunctorId, - const std::size_t pRecordTypeId, const TypeQ pQualifier) - : m_qualifier(pQualifier) - , m_recordTypeId(pRecordTypeId) - , m_record(pRecord) - , m_function(pFunction) - , m_namespace(pNamespace) - , m_functorIds({ pFunctorId }) { - } +#include "Function.h" +namespace rtl +{ +/* @constructor: Function() + @params: pNamespace - given namespace while registering the type. + * pRecord - given class/struct name, empty if this 'Function' represents a non-member functor + * pFunction - given name of the function as string. + * pFunctorId - 'FunctorId', generated for every functor being registered. + * pRecordTypeId - type id of class/struct if the functor is member-function, '0' for non-member-functions. + * pQualifier - whether the member-function is const or non-const. methodQ::None for non-member & static-member functions. + * 'Function' object is created for every functor (member/non-member) being registered. +*/ Function::Function(const std::string_view pNamespace, const std::string_view pRecord, + const std::string_view pFunction, const detail::FunctorId& pFunctorId, + const std::size_t pRecordTypeId, const detail::methodQ pQualifier) + : m_qualifier(pQualifier) + , m_recordTypeId(pRecordTypeId) + , m_record(pRecord) + , m_function(pFunction) + , m_namespace(pNamespace) + , m_functorIds({ pFunctorId }) { + } - /* @constructor: Function() - @params: pOther - 'Function' object associated with a constructor. - * pFunctorId - 'FunctorId', object associated with a destructor. - * pFunctorName - name of the destructor. - * this constructor is only called to create 'Function' object associated with destructor. - * the destructor 'FunctorId' is added to the 'Function' object associated with a constructor while registration. - * the very first registration of constructor adds the destructor lambda in the functor-container and sends its - 'FunctorId' with the 'Function' object associated with a constructor. - */ Function::Function(const Function& pOther, const detail::FunctorId& pFunctorId, - const std::string& pFunctorName) - : m_qualifier(pOther.m_qualifier) - , m_recordTypeId(pOther.m_recordTypeId) - , m_record(pOther.m_record) - , m_function(pFunctorName) - , m_namespace(pOther.m_namespace) - , m_functorIds({ pFunctorId }) { - } +/* @constructor: Function() + @params: pOther - 'Function' object associated with a constructor. + * pFunctorId - 'FunctorId', object associated with a copy-constructor. + * pFunctorName - name of the constructor. + * this constructor is only called to create 'Function' object associated with copy-constructor. + * the copy-constructor's 'FunctorId' is added to the 'Function' object associated with a constructor while registration. + * the very first registration of constructor adds the copy-constructor lambda in the functor-container and sends its + 'FunctorId' with the 'Function' object associated with a constructor. +*/ Function::Function(const Function& pOther, const detail::FunctorId& pFunctorId, + const std::string_view pFunctorName) + : m_qualifier(pOther.m_qualifier) + , m_recordTypeId(pOther.m_recordTypeId) + , m_record(pOther.m_record) + , m_function(pFunctorName) + , m_namespace(pOther.m_namespace) + , m_functorIds({ pFunctorId }) { + } - /* @method: hasSignatureId() - @param: const std::size_t& (signatureId to be found) - @return: the index of the functor in the functor-table. - * a 'Function' object may be associated with multiple functors in case of overloads. - * every overload will have unique 'FunctorId', contained by one 'Function' object. - * given signatureId is compared against the signatureId of all overloads registered. - */ const std::size_t Function::hasSignatureId(const std::size_t& pSignatureId) const - { - //simple linear-search, efficient for small set of elements. - for (const auto& functorId : m_functorIds) { - if (functorId.getSignatureId() == pSignatureId) { - return functorId.getIndex(); - } - } - return -1; - } +/* @method: addOverload() + @param: 'Function' object + * every 'Function' object produced while registration will have a single 'FunctorId' object, except constructors. + * for overloads, registered with the same name, the 'FunctorId' from the 'pOtherFunc' object will be added to this. + * if the same functor is registered again with the same name, it will be ignored. +*/ void Function::addOverload(const Function& pOtherFunc) const + { + const std::size_t& otherFuncSignId = pOtherFunc.m_functorIds[0].getSignatureId(); + //simple linear-search, efficient for small set of elements. + for (const auto& functorId : m_functorIds) { + if (functorId.getSignatureId() == otherFuncSignId) { - /* @method: addOverload() - @param: 'Function' object - * every 'Function' object produced while registration will have a single 'FunctorId' object, except constructors. - * for overloads, registered with the same name, the 'FunctorId' from the 'pOtherFunc' object will be added to this. - * if the same functor is registered again with the same name, it will be ignored. - */ void Function::addOverload(const Function& pOtherFunc) const - { - const std::size_t& otherFuncSignId = pOtherFunc.m_functorIds[0].getSignatureId(); - //simple linear-search, efficient for small set of elements. - for (const auto& functorId : m_functorIds) { - if (functorId.getSignatureId() == otherFuncSignId) { - return; //ignore and return since its already registered. - } - } + std::cout << "\n[WARNING] Multiple registrations of the same function-pointer detected." + << "\n function-pointer already registered as \"" << m_function << "\"" + << "\n This registration is ignored.\n"; - //add the 'functorId' of the overloaded functor. - m_functorIds.push_back(pOtherFunc.m_functorIds[0]); + return; //ignore and return since its already registered. + } } + //add the 'functorId' of the overloaded functor. + m_functorIds.push_back(pOtherFunc.m_functorIds[0]); } } \ No newline at end of file diff --git a/ReflectionTemplateLib/access/src/Instance.cpp b/ReflectionTemplateLib/access/src/Instance.cpp deleted file mode 100644 index 649c9e9d..00000000 --- a/ReflectionTemplateLib/access/src/Instance.cpp +++ /dev/null @@ -1,100 +0,0 @@ - -#include - -#include "TypeId.hpp" -#include "RStatus.h" -#include "Instance.h" -#include "Function.hpp" - -namespace { - - //global, used to assign to shared pointer with custom deleter. - static std::size_t g_instanceCount = 0; -} - -namespace rtl { - - namespace access - { - /* @method: isEmpty() - @return: bool - * checks if std::any object has value or not. - * objects constructed via reflection is held by std::any (instead of void*) - * if reflected constructor call fails, 'Insatnce' object returned with empty 'm_anyObject'. - */ const bool Instance::isEmpty() const { - return (!m_anyObject.has_value()); - } - - - /* @method: isConst() - @return: bool - * tells how the object held by 'm_anyObject' should be treated. - * every object constructed via reflected constructor call is a non-const object pointer. - * it can be made to treated as const by calling Instance::makeConst(). - */ const bool Instance::isConst() const { - return (m_qualifier == TypeQ::Const); - } - - - /* @method: getInstanceCount() - @return: std::size_t (g_instanceCount). - * returns the number of objects constructed via reflected constructor call. - * g_instanceCount is incremented only on successful reflected constructor call. - * g_instanceCount is decremented only when reflected destructor call. - */ std::size_t Instance::getInstanceCount() { - return g_instanceCount; - } - - - /* @method: makeConst() - @param: bool, (true by default) - * objects constructed via reflected constructor call, held by 'm_anyObject' as a non-const object pointer. - * 'm_qualifier' indicates how the object should be treated- as const or non-const. - * if 'm_qualifier' is TypeQ::Const, only const member function will be called on the object held by 'm_anyObject' - * if 'm_qualifier' is TypeQ::Mute,, only non-const member function will be called on the objject held by 'm_anyObject' - */ void Instance::makeConst(const bool& pCastAway) { - m_qualifier = (pCastAway ? TypeQ::Mute : TypeQ::Const); - } - - /* @constructor: Instance() - * creates 'Instance' with empty 'm_anyObject'. - * 'm_typeId' will be zero which indicates no-type. - * this constructor is called only when reflected constructor call fails. - */ Instance::Instance() - : m_qualifier(TypeQ::None) - , m_typeId(detail::TypeId<>::None) { - } - - //copy-constructor, public access. - Instance::Instance(const Instance& pOther) - : m_qualifier(pOther.m_qualifier) - , m_typeId(pOther.m_typeId) - , m_anyObject(pOther.m_anyObject) - , m_destructor(pOther.m_destructor) { - } - - - /* @constructor: Instance() - @params: 'const std::any&', contains pointer to the allocated object via reflection constructor call. - * 'const RStatus&', status returned via reflection constructor call. - * 'const Function&', callable 'Function', calls the reflecetd destructor. - * creates 'Instance' containing pointer to the allocated object via reflection constructor call. - * this constructor is called only on successful object creation on heap via reflected constructor call. - * 'm_destructor' (shared_ptr) is given a custom deleter, which calls destructor on the allocated(via reflection) object. - * 'm_destructor' holds a dummy void* pointer (address of 'g_instanceCount'), for which is a primitive type. - * this is done to avoid dynamic allocation of 'Instance' object to manage it with 'shared_ptr'. - * shared_ptr('m_destructor') holds the dummy void* but calls the actual destructor which destroys the object constructed(via reflection). - */ Instance::Instance(const std::any& pRetObj, const RStatus& pStatus, const Function& pDctor) - : m_qualifier(TypeQ::Mute) - , m_typeId(pStatus.getTypeId()) - , m_anyObject(pRetObj) - , m_destructor(&g_instanceCount, [=](void* ptr) - { - pDctor(pRetObj); - (*static_cast(ptr))--; - }) - { - g_instanceCount++; - } - } -} \ No newline at end of file diff --git a/ReflectionTemplateLib/access/src/Method.cpp b/ReflectionTemplateLib/access/src/Method.cpp deleted file mode 100644 index 89e2f06a..00000000 --- a/ReflectionTemplateLib/access/src/Method.cpp +++ /dev/null @@ -1,24 +0,0 @@ - -#include "Method.h" - -namespace rtl { - - namespace access - { - Method::Method(const Function& pFunction) - : Function(pFunction) { - } - - - Method::Method(const Function& pFunction, const detail::FunctorId& pFunctorId, const std::string& pFunctorName) - : Function(pFunction, pFunctorId, pFunctorName) { - } - - - Method Method::getDestructorMethod(const Function& pFunction, const detail::FunctorId& pFunctorId) - { - const std::string dctorStr = CtorName::dctor(pFunction.getRecordName()); - return Method(pFunction, pFunctorId, dctorStr); - } - } -} \ No newline at end of file diff --git a/ReflectionTemplateLib/access/src/RStatus.cpp b/ReflectionTemplateLib/access/src/RStatus.cpp deleted file mode 100644 index 06fd8988..00000000 --- a/ReflectionTemplateLib/access/src/RStatus.cpp +++ /dev/null @@ -1,23 +0,0 @@ - -#include "RStatus.h" - -namespace rtl { - - namespace access { - - RStatus::RStatus(const Error pCallStatus) - : m_callStatus(pCallStatus) - , m_typeQualifier(TypeQ::None) - //no type is represented by value '0'. - , m_typeId(detail::TypeId<>::None) { - } - - - RStatus::RStatus(const std::any& pRetObj, const std::size_t pTypeId, const TypeQ pQualifier) - : m_callStatus(Error::None) - , m_typeQualifier(pQualifier) - , m_returnObj(pRetObj) - , m_typeId(pTypeId) { - } - } -} \ No newline at end of file diff --git a/ReflectionTemplateLib/access/src/Record.cpp b/ReflectionTemplateLib/access/src/Record.cpp deleted file mode 100644 index 4df8986d..00000000 --- a/ReflectionTemplateLib/access/src/Record.cpp +++ /dev/null @@ -1,129 +0,0 @@ - -#include "Record.h" -#include "Method.h" -#include "RStatus.h" -#include "Instance.h" -#include "Constants.h" -#include "Function.hpp" - -namespace rtl { - - namespace access - { - Record::Record(const std::string& pRecordName) - : m_recordName(pRecordName) - { - } - - - /* @method: getFunctionsMap - @param: none - @return: std::unordered_map< std::string, access::Method >& - * get set of all registered methods contained by the class/struct represented by this 'Record'. - * provides 'mutable' map, which only detail::CxxReflection class can access. - */ std::unordered_map< std::string, access::Method >& Record::getFunctionsMap() const - { - return m_methods; - } - - - /* @method: getMethodMap - @param: none - @return: const std::unordered_map< std::string, access::Method >& - * get set of all registered methods contained by the class/struct represented by this 'Record'. - * provides 'const' map, publicly accessible. - */ const std::unordered_map& Record::getMethodMap() const - { - return m_methods; - } - - - /* @method: getMethod - @param: const std::string& (name of the method) - @return: std::optional - * if the method isn't found by the given name, std::nullopt is returned. - */ std::optional Record::getMethod(const std::string& pMethod) const - { - const auto& itr = m_methods.find(pMethod); - if (itr != m_methods.end()) { - return std::optional(itr->second); - } - return std::nullopt; - } - - - /* @method: clone - @param: Instance& (containing class/struct's object represented by this 'Record') - @return: std::pair (RStatus: call success or not, Instance: containing copy constructed object) - * calls copy constructor of class/struct represented by this 'Record' - * creates copy of the object wrapped inside 'Instance' object. - * returns 'RStatus' object indicating the success of the reflection call with other infos. - */ const std::pair Record::clone(Instance& pOther) const - { - //validate the source object, should not be empty. - if (pOther.isEmpty()) { - //return empty instance with error status. - return std::make_pair(RStatus(Error::EmptyInstance), Instance()); - } - - const std::string& dctor = CtorName::dctor(m_recordName); - const std::string& copyStr = CtorName::copy(m_recordName); - const std::string& constCopyStr = CtorName::constCopy(m_recordName); - - std::optional destructor = getMethod(dctor); - std::optional constCopyCtor = getMethod(constCopyStr); - - //if the object is const, only copy constructor with 'const&' can be called on it. - if (pOther.isConst()) - { - if (constCopyCtor) - { - /* type of the object wrapped under source 'Instance' should match with type of the class/struct - associated by constructor ('Function')object. - */ if (constCopyCtor->getRecordTypeId() != pOther.getTypeId()) { - //if source instance & ctor type didn't match, return empty instance with error status. - return std::make_pair(RStatus(Error::InstanceTypeMismatch), Instance()); - } - //object and type validated. call the const-copy-constructor. - RStatus status = (*constCopyCtor)(pOther.get()); - return std::make_pair(status, Instance(status.getReturn(), status, *destructor)); - } - else { - //if the object is 'const' and no constructor found accepting 'const&' - return std::make_pair(RStatus(Error::ConstCopyConstructorNotFound), Instance()); - } - } - else { - //if the source 'Instance' is non-const, find copy-constructor taking non-const ref. - std::optional copyCtor = getMethod(copyStr); - if (copyCtor) - { - /* type of the object wrapped under source 'Instance' should match with type of the class/struct - associated by constructor ('Function')object. - */ if (copyCtor->getRecordTypeId() != pOther.getTypeId()) { - //if source instance & ctor type didn't match, return empty instance with error status. - return std::make_pair(RStatus(Error::InstanceTypeMismatch), Instance()); - } - //object and type validated. call the non-const-copy-constructor. - RStatus status = (*copyCtor)(pOther.get()); - return std::make_pair(status, Instance(status.getReturn(), status, *destructor)); - } - //if copy-constructor taking non-const ref not found, and with const-ref found, use that copy constructor. - else if (constCopyCtor) - { - /* type of the object wrapped under source 'Instance' should match with type of the class/struct - associated by constructor ('Function')object. - */ if (constCopyCtor->getRecordTypeId() != pOther.getTypeId()) { - //if source instance & ctor type didn't match, return empty instance with error status. - return std::make_pair(RStatus(Error::InstanceTypeMismatch), Instance()); - } - //object and type validated. call the const-copy-constructor. - RStatus status = (*constCopyCtor)(pOther.get()); - return std::make_pair(status, Instance(status.getReturn(), status, *destructor)); - } - } - //if no registered copy constructor found, return empty instance with error status. - return std::make_pair(RStatus(Error::CopyConstructorNotFound), Instance()); - } - } -} \ No newline at end of file diff --git a/ReflectionTemplateLib/builder/CMakeLists.txt b/ReflectionTemplateLib/builder/CMakeLists.txt index 7956d660..0484adc6 100644 --- a/ReflectionTemplateLib/builder/CMakeLists.txt +++ b/ReflectionTemplateLib/builder/CMakeLists.txt @@ -6,7 +6,6 @@ SET(COMMON_HEADERS SET(LOCAL_HEADERS "${CMAKE_CURRENT_LIST_DIR}/inc/ConstructorBuilder.h" - "${CMAKE_CURRENT_LIST_DIR}/inc/ConstructorBuilder.hpp" "${CMAKE_CURRENT_LIST_DIR}/inc/Builder.h" "${CMAKE_CURRENT_LIST_DIR}/inc/Builder.hpp" "${CMAKE_CURRENT_LIST_DIR}/inc/RecordBuilder.h" diff --git a/ReflectionTemplateLib/builder/inc/Builder.h b/ReflectionTemplateLib/builder/inc/Builder.h index 88818597..687de0f2 100644 --- a/ReflectionTemplateLib/builder/inc/Builder.h +++ b/ReflectionTemplateLib/builder/inc/Builder.h @@ -1,182 +1,194 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include "Function.h" -#include "ReflectionBuilder.hpp" +#include "ReflectionBuilder.h" namespace rtl { namespace builder { - /* @struct: Builder - @param: specialized with TypeQ, - * TypeQ::Mute - provides interface to register member funtion. - * TypeQ::Const - provides interface to register const-member funtions. - * TypeQ::None - provides interface to register non-member and static member funtions. + struct CtorBuilder : protected detail::ReflectionBuilder + { + CtorBuilder(const std::string_view pNamespace, const std::string_view pRecord, + const std::string_view pFunction, std::size_t pRecordId); + + template + const Function build() const; + }; + + + /* @struct: Builder + @param: specialized with methodQ, + * methodQ::NonConst - provides interface to register member funtion. + * methodQ::Const - provides interface to register const-member funtions. + * methodQ::None - provides interface to register non-member and static member funtions. @param: * _signature: arguments types of functions pointers or constructors (auto deduced/explicitly specified). * provides interface to register all sort of functions, methods & constructors. * every specialization has a 'build()' function, which accepts a function pointer. * function pointer can be non-member or member(static/const/non-const) functions. - */ template + */ template struct Builder; } namespace builder { - /* @struct: Builder + /* @struct: Builder * specialized specifically to register overloaded non-member & static member functions with no arguments. * Objects of this class will be created & returned by these functions, - * - Reflect::function(..) + * - type::function(..) * - RecordBuilder<_recordType>::methodStatic(..) * with template parameter is only 'void', explicitly specified. */ template<> - struct Builder : protected detail::ReflectionBuilder + struct Builder : protected detail::ReflectionBuilder { - Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction); + Builder(std::size_t pRecordId, const std::string_view pFunction, + const std::string_view pNamespace); template - constexpr const access::Function build(_returnType(*pFunctor)()) const; + const Function build(_returnType(*pFunctor)()) const; }; - /* @struct: Builder + /* @struct: Builder * specialized specifically to register overloaded non-member & static member functions with any arguments. * Objects of this class will be created & returned by these functions, - * - Reflect::function<...>(..) + * - type::function<...>(..) * - RecordBuilder<_recordType>::methodStatic<...>(..) * with template parameters can be anything, explicitly specified. */ template - struct Builder : protected detail::ReflectionBuilder + struct Builder : protected detail::ReflectionBuilder { - Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction); + Builder(std::size_t pRecordId, const std::string_view pFunction, + const std::string_view pNamespace); template - constexpr const access::Function build(_returnType(*pFunctor)(_signature...)) const; + const Function build(_returnType(*pFunctor)(_signature...)) const; }; - /* @struct: Builder + /* @struct: Builder * specialized specifically to register non-member functions with any signature and with no overloads. * Objects of this class will be created & returned by these functions, - * - Reflect::function(..) + * - type::function(..) * - RecordBuilder<_recordType>::methodStatic(..) * with no template parameters specified. */ template<> - struct Builder : protected detail::ReflectionBuilder + struct Builder : protected detail::ReflectionBuilder { - Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction); + Builder(std::size_t pRecordId, const std::string_view pFunction, + const std::string_view pNamespace); template - constexpr const access::Function build(_returnType(*pFunctor)(_signature...)) const; + const Function build(_returnType(*pFunctor)(_signature...)) const; }; } namespace builder { - /* @struct: Builder + /* @struct: Builder * specialized specifically to register overloaded const-member-functions with no arguments. * Objects of this class will be created & returned by function, * - RecordBuilder<_recordType>::methodConst(..) * with template parameters is only 'void' explicitly specified. */ template<> - struct Builder : protected detail::ReflectionBuilder + struct Builder : protected detail::ReflectionBuilder { - Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction); + Builder(const std::string_view pFunction, std::size_t pRecordId); template - constexpr const access::Function build(_returnType(_recordType::* pFunctor)() const) const; + const Function build(_returnType(_recordType::* pFunctor)() const) const; }; - /* @struct: Builder + /* @struct: Builder * specialized specifically to register overloaded const-member-functions with any arguments. * Objects of this class will be created & returned by function, * - RecordBuilder<_recordType>::methodConst<...>(..) * with template parameters can be anything, explicitly specified. */ template - struct Builder : protected detail::ReflectionBuilder + struct Builder : protected detail::ReflectionBuilder { - Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction); + Builder(const std::string_view pFunction, std::size_t pRecordId); template - constexpr const access::Function build(_returnType(_recordType::* pFunctor)(_signature...) const) const; + const Function build(_returnType(_recordType::* pFunctor)(_signature...) const) const; }; - /* @struct: Builder + /* @struct: Builder * specialized specifically to register non-overloaded const-member-functions with any arguments. * Objects of this class will be created & returned by function, * - RecordBuilder<_recordType>::methodConst() * with no template parameters specified. */ template<> - struct Builder : protected detail::ReflectionBuilder + struct Builder : protected detail::ReflectionBuilder { - Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction); + Builder(const std::string_view pFunction, std::size_t pRecordId); template - constexpr const access::Function build(_returnType(_recordType::* pFunctor)(_signature...) const) const; + const Function build(_returnType(_recordType::* pFunctor)(_signature...) const) const; }; } namespace builder { - /* @struct: Builder + /* @struct: Builder * specialized specifically to register overloaded non-const-member-functions with no arguments. * Objects of this class will be created & returned by function, * - RecordBuilder<_recordType>::method(..) * with template parameters is only 'void' explicitly specified. */ template<> - struct Builder : protected detail::ReflectionBuilder + struct Builder : protected detail::ReflectionBuilder { - Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction); + Builder(const std::string_view pFunction, std::size_t pRecordId); template - constexpr const access::Function build(_returnType(_recordType::* pFunctor)()) const; + const Function build(_returnType(_recordType::* pFunctor)()) const; }; - /* @struct: Builder + /* @struct: Builder * specialized specifically to register overloaded non-const-member-functions with no arguments. * Objects of this class will be created & returned by function, * - RecordBuilder<_recordType>::method(..) * with template parameters is only 'void' explicitly specified. */ template - struct Builder : protected detail::ReflectionBuilder + struct Builder : protected detail::ReflectionBuilder { - Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction); + Builder(const std::string_view pFunction, std::size_t pRecordId); template - constexpr const access::Function build(_returnType(_recordType::* pFunctor)(_signature...)) const; + const Function build(_returnType(_recordType::* pFunctor)(_signature...)) const; }; - /* @struct: Builder + /* @struct: Builder * specialized specifically to register non-overloaded non-const-member-functions and constructors with any arguments. * Objects of this class will be created & returned by function, * - RecordBuilder<_recordType>::method() - with no template parameters specified. * - RecordBuilder<_recordType>::constructor<...>() - template parameters can be anything or none, explicitly specified. */ template<> - struct Builder : protected detail::ReflectionBuilder + struct Builder : protected detail::ReflectionBuilder { - Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction); - - template - constexpr const access::Function build() const; + Builder(const std::string_view pFunction, std::size_t pRecordId); template - constexpr const access::Function build(_returnType(_recordType::* pFunctor)(_signature...)) const; + const Function build(_returnType(_recordType::* pFunctor)(_signature...)) const; }; } } \ No newline at end of file diff --git a/ReflectionTemplateLib/builder/inc/Builder.hpp b/ReflectionTemplateLib/builder/inc/Builder.hpp index 46bba871..ee74df9c 100644 --- a/ReflectionTemplateLib/builder/inc/Builder.hpp +++ b/ReflectionTemplateLib/builder/inc/Builder.hpp @@ -1,24 +1,61 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once +#include "TypeId.h" #include "Builder.h" +#include "ReflectionBuilder.hpp" -namespace rtl { +namespace rtl +{ + namespace builder + { + inline CtorBuilder::CtorBuilder(const std::string_view pNamespace, const std::string_view pRecord, + const std::string_view pFunction, std::size_t pRecordId) + : ReflectionBuilder(pFunction, pRecordId, pNamespace, pRecord) { + } + /* @method: build() + @param: none + @return: 'Function' object. + * accepts no arguments, builds copy constructor which takes const object source. + * called on object returned by 'RecordBuilder<_recordType>::constructor<...>()' + * template params <...>, explicitly specified. + * calling with zero template params will build the default constructor ie, 'RecordBuilder<_recordType>::constructor()' + */ template + inline const Function CtorBuilder::build() const + { + return buildConstructor<_recordType, _signature...>(); + } + } +} + + +namespace rtl +{ namespace builder { - inline Builder::Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction) - : ReflectionBuilder(pNamespace, pRecord, pFunction) { + inline Builder::Builder(std::size_t pRecordId, const std::string_view pFunction, const std::string_view pNamespace) + : ReflectionBuilder(pFunction, pRecordId, pNamespace) { } /* @method: build() @param: _returnType(*)(_signature...) - @return: 'access::Function' object. + @return: 'Function' object. * accepts all non-member and static-member function pointer. - * called on the objects returned by 'Reflect::function()' & 'RecordBuilder<_recordType>::methodStatic(..)'. + * called on the objects returned by 'type::function()' & 'RecordBuilder<_recordType>::methodStatic(..)'. * template params are auto deduced from the function pointer passed. */ template - inline constexpr const access::Function Builder::build(_returnType(*pFunctor)(_signature...)) const + inline const Function Builder::build(_returnType(*pFunctor)(_signature...)) const { return buildFunctor(pFunctor); } @@ -26,20 +63,19 @@ namespace rtl { namespace builder - { - inline Builder::Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction) - : ReflectionBuilder(pNamespace, pRecord, pFunction) { - } + { + inline Builder::Builder(std::size_t pRecordId, const std::string_view pFunction, const std::string_view pNamespace) + : ReflectionBuilder(pFunction, pRecordId, pNamespace) + { } /* @method: build() @param: _returnType(*)() - @return: 'access::Function' object. + @return: 'Function' object. * accepts a non-member or static-member function pointer with no arguments. - * called on objects returned by 'Reflect::function(..)' & 'RecordBuilder<_recordType>::methodStatic(..)' + * called on objects returned by 'type::function(..)' & 'RecordBuilder<_recordType>::methodStatic(..)' * template param 'void' is explicitly specified. */ template - inline constexpr const access::Function Builder::build(_returnType(*pFunctor)()) const + inline const Function Builder::build(_returnType(*pFunctor)()) const { return buildFunctor(pFunctor); } @@ -49,21 +85,20 @@ namespace rtl { namespace builder { template - inline Builder::Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction) - : ReflectionBuilder(pNamespace, pRecord, pFunction) { - } + inline Builder::Builder(std::size_t pRecordId, const std::string_view pFunction, const std::string_view pNamespace) + : ReflectionBuilder(pFunction, pRecordId, pNamespace) + { } /* @method: build() @param: _returnType(*)(_signature...) - @return: 'access::Function' object. + @return: 'Function' object. * it accepts a non-member or static-member function pointer. - * called on objects returned by 'Reflect::function<...>(..)' & 'RecordBuilder<_recordType>::methodStatic<...>(..)'. + * called on objects returned by 'type::function<...>(..)' & 'RecordBuilder<_recordType>::methodStatic<...>(..)'. * template params are explicitly specified. */ template template - inline constexpr const access::Function Builder::build(_returnType(*pFunctor)(_signature...)) const + inline const Function Builder::build(_returnType(*pFunctor)(_signature...)) const { return buildFunctor(pFunctor); } @@ -72,19 +107,18 @@ namespace rtl { namespace builder { - inline Builder::Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction) - : ReflectionBuilder(pNamespace, pRecord, pFunction) { - } + inline Builder::Builder(const std::string_view pFunction, std::size_t pRecordId) + : ReflectionBuilder(pFunction, pRecordId) + { } /* @method: build() @param: _returnType(_recordType::*)(_signature...) const. - @return: 'access::Function' object. + @return: 'Function' object. * accepts function pointer of a const-member-function with any signature. * called on object returned by 'RecordBuilder<_recordType>::methodConst()' * template params will be auto deduced from the function pointer passed. */ template - inline constexpr const access::Function Builder::build(_returnType(_recordType::* pFunctor)(_signature...) const) const + inline const Function Builder::build(_returnType(_recordType::* pFunctor)(_signature...) const) const { return buildMethodFunctor(pFunctor); } @@ -93,19 +127,18 @@ namespace rtl { namespace builder { - inline Builder::Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction) - : ReflectionBuilder(pNamespace, pRecord, pFunction) { - } + inline Builder::Builder(const std::string_view pFunction, std::size_t pRecordId) + : ReflectionBuilder(pFunction, pRecordId) + { } /* @method: build() @param: _returnType(_recordType::*)() const. - @return: 'access::Function' object. + @return: 'Function' object. * accepts a const-member-function pointer with no arguments. * called on object returned by 'RecordBuilder<_recordType>::methodConst()' * template param 'void' is explicitly specified. */ template - inline constexpr const access::Function Builder::build(_returnType(_recordType::* pFunctor)() const) const + inline const Function Builder::build(_returnType(_recordType::* pFunctor)() const) const { return buildMethodFunctor(pFunctor); } @@ -115,20 +148,19 @@ namespace rtl { namespace builder { template - inline Builder::Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction) - : ReflectionBuilder(pNamespace, pRecord, pFunction) { - } + inline Builder::Builder(const std::string_view pFunction, std::size_t pRecordId) + : ReflectionBuilder(pFunction, pRecordId) + { } /* @method: build() @param: _returnType(_recordType::*)(_signature...) const. - @return: 'access::Function' object. + @return: 'Function' object. * accepts a const-member-function pointer with any arguments. * called on object returned by 'RecordBuilder<_recordType>::methodConst<...>()' * template param are explicitly specified. */ template template - inline constexpr const access::Function Builder::build(_returnType(_recordType::* pFunctor)(_signature...) const) const + inline const Function Builder::build(_returnType(_recordType::* pFunctor)(_signature...) const) const { return buildMethodFunctor(pFunctor); } @@ -137,48 +169,19 @@ namespace rtl { namespace builder { - inline Builder::Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction) - : ReflectionBuilder(pNamespace, pRecord, pFunction) { - } - - - /* @method: build() - @param: none - @return: 'access::Function' object. - * accepts no arguments, builds copy constructor which takes const object source. - * called on object returned by 'RecordBuilder<_recordType>::constructor<...>()' - * template params <...>, explicitly specified. - * calling with zero template params will build the default constructor ie, 'RecordBuilder<_recordType>::constructor()' - */ template - inline constexpr const access::Function Builder::build() const - { - //this code-block is retained by compiler, if copy constructor with non-const ref('_recordType&') is being registered. - if constexpr (std::is_same_v<_recordType&, typename detail::TypeId<_signature...>::HEAD>) - { - return buildCopyConstructor<_recordType, _signature...>(); - } - //this code-block is retained by compiler, if copy constructor with const-ref('const _recordType&') is being registered. - else if constexpr (std::is_same_v::HEAD>) - { - return buildConstCopyConstructor<_recordType, _signature...>(); - } - //if any other constructor except, copy constructor is being registered, this code-block will be retained. - else - { - return buildConstructor<_recordType, _signature...>(); - } - } + inline Builder::Builder(const std::string_view pFunction, std::size_t pRecordId) + : ReflectionBuilder(pFunction, pRecordId) + { } /* @method: build() @param: _returnType(_recordType::*)(_signature...) - @return: 'access::Function' object. + @return: 'Function' object. * accepts a non-const-member-function pointer with any arguments. * called on object returned by 'RecordBuilder<_recordType>::method()' * template params are auto deduced from the pointer passed. */ template - inline constexpr const access::Function Builder::build(_returnType(_recordType::* pFunctor)(_signature...)) const + inline const Function Builder::build(_returnType(_recordType::* pFunctor)(_signature...)) const { return buildMethodFunctor(pFunctor); } @@ -187,20 +190,19 @@ namespace rtl { namespace builder { - inline Builder::Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction) - : ReflectionBuilder(pNamespace, pRecord, pFunction) { - } + inline Builder::Builder(const std::string_view pFunction, std::size_t pRecordId) + : ReflectionBuilder(pFunction, pRecordId) + { } /* @method: build() @param: _returnType(_recordType::*)() - @return: 'access::Function' object. + @return: 'Function' object. * accepts a non-const-member-function pointer with no arguments. * called on object returned by 'RecordBuilder<_recordType>::method()' * template param 'void' is explicitly specified. */ template - inline constexpr const access::Function Builder::build(_returnType(_recordType::* pFunctor)()) const + inline const Function Builder::build(_returnType(_recordType::* pFunctor)()) const { return buildMethodFunctor(pFunctor); } @@ -210,20 +212,19 @@ namespace rtl { namespace builder { template - inline Builder::Builder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction) - : ReflectionBuilder(pNamespace, pRecord, pFunction) { - } + inline Builder::Builder(const std::string_view pFunction, std::size_t pRecordId) + : ReflectionBuilder(pFunction, pRecordId) + { } /* @method: build() @param: _returnType(_recordType::*)(_signature...) - @return: 'access::Function' object. + @return: 'Function' object. * accepts a non-const-member-function pointer with any arguments. * called on object returned by 'RecordBuilder<_recordType>::method<...>()' * template params are explicitly specified. */ template template - inline constexpr const access::Function Builder::build(_returnType(_recordType::* pFunctor)(_signature...)) const + inline const Function Builder::build(_returnType(_recordType::* pFunctor)(_signature...)) const { return buildMethodFunctor(pFunctor); } diff --git a/ReflectionTemplateLib/builder/inc/ConstructorBuilder.h b/ReflectionTemplateLib/builder/inc/ConstructorBuilder.h index c29db0ee..94e4ba3f 100644 --- a/ReflectionTemplateLib/builder/inc/ConstructorBuilder.h +++ b/ReflectionTemplateLib/builder/inc/ConstructorBuilder.h @@ -1,3 +1,14 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include "Constants.h" @@ -9,34 +20,46 @@ namespace rtl { /* @class: ConstructorBuilder @param: _recordType - struct/class type. * _signature...- constructor args type (none/_record&/const _record& or any combination of parameters) - * provides interface to register constructors/destructor of a class/struct. - * when the very first constructor(any- copy/default/parametrized) is registered, destructor gets registered implicitly. + * provides interface to register constructors of a class/struct. + * when the very first constructor(any- default/parametrized) is registered, copy-constructor gets registered implicitly. * all the objects are created via reflection are on heap, using 'new'. * the constructed objects are returned wrapped in 'Instance' object, with type erased. * lifetime of created objects are managed using 'shared_ptr'. */ template - class ConstructorBuilder + struct ConstructorBuilder { //given name of the class/struct. - const std::string& m_record; + const std::string_view m_record; //given name of the namespace. - const std::string& m_namespace; + const std::string_view m_namespace; - /* type of constructor to be registered. - FunctorType::Ctor - default/parametrized constructor. - FunctorType::CopyCtor - copy constructor args, '_recordType&' - FunctorType::CopyCtorConst - copy constructor args, 'const _recordType&' - */ const FunctorType m_ctorType; + public: - ConstructorBuilder() = delete; + ConstructorBuilder() + : m_record("") + , m_namespace("") + { } - public: + ConstructorBuilder(const std::string_view pNamespace, const std::string_view pRecord) + : m_record(pRecord) + , m_namespace(pNamespace) + { } + + /* @method: build() + @param: none + @return: 'Function' object. + * constructs temparory object of class Builder with given class/struct, namespace name & constructor type. + * forwards the call to Builder::build(). + */ const Function build() const + { + // Check if the constructor is not deleted and publicly accessible (excluding default constructor). + const bool isAccessible = (sizeof...(_ctorSignature) == 0 || std::is_constructible_v<_recordType, _ctorSignature...>); + static_assert(isAccessible, "The specified constructor is either deleted or not publicly accessible."); - ConstructorBuilder(const std::string& pNamespace, const std::string& pRecord, - const FunctorType& pCtorType); - - inline constexpr const access::Function build() const; - }; - } + return CtorBuilder(m_namespace, m_record, std::string_view(detail::ctor_name(m_record)), + detail::TypeId<_recordType>::get()).build<_recordType, _ctorSignature...>(); + } + }; + } } \ No newline at end of file diff --git a/ReflectionTemplateLib/builder/inc/ConstructorBuilder.hpp b/ReflectionTemplateLib/builder/inc/ConstructorBuilder.hpp deleted file mode 100644 index e6608590..00000000 --- a/ReflectionTemplateLib/builder/inc/ConstructorBuilder.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "Function.h" -#include "Builder.hpp" -#include "ConstructorBuilder.h" - -namespace rtl { - - namespace builder - { - template - inline ConstructorBuilder<_recordType, _ctorSignature...>::ConstructorBuilder(const std::string& pNamespace, const std::string& pRecord, - const FunctorType& pCtorType) - : m_record(pRecord) - , m_namespace(pNamespace) - , m_ctorType(pCtorType) - { - } - - - /* @method: build() - @param: none - @return: 'Function' object. - * constructs temparory object of class Builder with given class/struct, namespace name & constructor type. - * forwards the call to Builder::build(). - */ template - inline constexpr const access::Function ConstructorBuilder<_recordType, _ctorSignature...>::build() const - { - const auto& ctorName = (m_ctorType == FunctorType::CopyCtor ? CtorName::copy(m_record) : - (m_ctorType == FunctorType::CopyCtorConst ? CtorName::constCopy(m_record) : CtorName::ctor(m_record))); - - return Builder(m_namespace, m_record, ctorName).build<_recordType, _ctorSignature...>(); - } - } -} \ No newline at end of file diff --git a/ReflectionTemplateLib/builder/inc/RecordBuilder.h b/ReflectionTemplateLib/builder/inc/RecordBuilder.h index 21970f2c..da6db803 100644 --- a/ReflectionTemplateLib/builder/inc/RecordBuilder.h +++ b/ReflectionTemplateLib/builder/inc/RecordBuilder.h @@ -1,5 +1,18 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once +#include + #include "Function.h" namespace rtl { @@ -7,38 +20,49 @@ namespace rtl { namespace builder { template - class ConstructorBuilder; + struct ConstructorBuilder; /* @class: RecordBuilder @param: <_recordType>, a struct/class type. - * provides interface to register member-function & constructors/destructor of a class/struct. + * provides interface to register member-function & constructors of a class/struct. */ template class RecordBuilder { - const std::string& m_record; - const std::string& m_namespace; + const std::string_view m_record; + const std::string_view m_namespace; + const std::size_t m_recordId; public: - RecordBuilder(const std::string& pNamespace, const std::string& pRecord); + RecordBuilder(const std::string_view pNamespace, const std::string_view pRecord, std::size_t pRecordId); + + const Function build() const; + }; - template - constexpr const ConstructorBuilder<_recordType, _signature...> constructor() const; - constexpr const Builder method(const std::string& pFunction) const; + /* @class: RecordBuilder + @param: <_recordType>, a struct/class type. + * provides interface to register member-function & constructors of a class/struct. + */ template + struct MethodBuilder + { + const Builder method(const std::string_view pFunction) const; + + const Builder methodConst(const std::string_view pFunction) const; - constexpr const Builder methodStatic(const std::string& pFunction) const; + const Builder methodStatic(const std::string_view pFunction) const; - constexpr const Builder methodConst(const std::string& pFunction) const; + template + const Builder method(const std::string_view pFunction) const; template - constexpr const Builder method(const std::string& pFunction) const; + const Builder methodConst(const std::string_view pFunction) const; template - constexpr const Builder methodStatic(const std::string& pFunction) const; + const Builder methodStatic(const std::string_view pFunction) const; template - constexpr const Builder methodConst(const std::string& pFunction) const; + constexpr const ConstructorBuilder<_recordType, traits::remove_const_n_ref_t<_signature>...> constructor() const; }; } } \ No newline at end of file diff --git a/ReflectionTemplateLib/builder/inc/RecordBuilder.hpp b/ReflectionTemplateLib/builder/inc/RecordBuilder.hpp index 5de80103..b05f5b0b 100644 --- a/ReflectionTemplateLib/builder/inc/RecordBuilder.hpp +++ b/ReflectionTemplateLib/builder/inc/RecordBuilder.hpp @@ -1,133 +1,146 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once +#include "rtl_traits.h" #include "RecordBuilder.h" -#include "ConstructorBuilder.hpp" +#include "ConstructorBuilder.h" + +namespace rtl::builder +{ + template + inline RecordBuilder<_recordType>::RecordBuilder(const std::string_view pNamespace, const std::string_view pRecord, std::size_t pRecordId) + : m_record(pRecord) + , m_namespace(pNamespace) + , m_recordId(pRecordId) + { } + + template + inline const Function RecordBuilder<_recordType>::build() const + { + return ConstructorBuilder<_recordType>(m_namespace, m_record).build(); + } +} + + +namespace rtl::builder +{ +/* @method: constructor<...>() + @param: none + @return: ConstructorBuilder<_recordType, _signature...> + * the copy constructors params are detected at compile time only. + * template params <...> - any combination of parameters. +*/ template + template + inline constexpr const ConstructorBuilder<_recordType, traits::remove_const_n_ref_t<_signature>...> MethodBuilder<_recordType>::constructor() const + { + constexpr bool isDefaultCtor = (sizeof...(_signature) == 0); + constexpr bool isCopyOrMoveCtor = (sizeof...(_signature) == 1 && traits::is_first_type_same_v<_recordType, _signature...>); + constexpr bool isDeclearedCtor = rtl::traits::has_constructor<_recordType, _signature...>; + + static_assert(!isDefaultCtor, "Default-constructor registration detected! It is implicitly registered with the Type."); + static_assert(!isCopyOrMoveCtor, "Copy/Move-constructor registration detected! It is implicitly registered with the Type."); + static_assert(isDeclearedCtor, "Constructor with given signature is not valid or declearation not found."); + + return ConstructorBuilder<_recordType, traits::remove_const_n_ref_t<_signature>...>(); + } + + +/* @method: methodStatic() + @param: std::string, name of function as string. + @return: Builder + * registers only static member functions. + * used for registering unique static member function, if overload exists, use templated version 'methodStatic<...>()'. + * the 'build(..)' called on return object will accepts static member function pointer only. + * compiler error on 'build(..)' if non-static member or non-member function pointer is passed. +*/ template + inline const Builder MethodBuilder<_recordType>::methodStatic(const std::string_view pFunction) const + { + return Builder(detail::TypeId<_recordType>::get(), pFunction, ""); + } + + +/* @method: methodStatic<...>() + @param: std::string, name of function as string. + @return: Builder + * registers only static member functions. + * used for registering overloads, if unique member function, use non-templated version 'methodStatic()'. + * template parameters must be explicitly specified, should be exactly same as the member-function being registered. + * the 'build(..)' called on return object will accepts static member function pointer only. + * compiler error on 'build(..)' if const member or non-member function pointer is passed. +*/ template + template + inline const Builder MethodBuilder<_recordType>::methodStatic(const std::string_view pFunction) const + { + return Builder(detail::TypeId<_recordType>::get(), pFunction, ""); + } + + +/* @method: method() + @param: std::string, name of function as string. + @return: Builder + * registers non-const, non-static member functions. + * the 'build(..)' called on return object will accepts non-const, non-static member-function-pointer only. + * compiler error on 'build(..)' if const, static member or non-member function pointer is passed. +*/ template + inline const Builder MethodBuilder<_recordType>::method(const std::string_view pFunction) const + { + return Builder(pFunction, detail::TypeId<_recordType>::get()); + } + + +/* @method: methodConst() + @param: std::string, name of function as string. + @return: Builder + * registers const member functions. + * used for registering unique member function, if overload exists, use templated version 'methodConst<...>()'. + * template parameters must be explicitly specified, should be exactly same as the member-function being registered. + * the 'build(..)' called on return object will accepts non-const member-function-pointer only. + * compiler error 'build(..)' if non-const, static member or non-member function pointer is passed. +*/ template + inline const Builder MethodBuilder<_recordType>::methodConst(const std::string_view pFunction) const + { + return Builder(pFunction, detail::TypeId<_recordType>::get()); + } + + +/* @method: method() + @param: std::string, name of function as string. + @return: Builder + * registers non-const member functions. + * used for registering overloads, for unique member function, use non-templated version 'method()'. + * template parameters must be explicitly specified, should be exactly same as the member-function being registered. + * the 'build(..)' called on return object will accepts non-const member-function-pointer only. + * compiler error on 'build(..)' if const, static member or non-member function pointer is passed. +*/ template + template + inline const Builder MethodBuilder<_recordType>::method(const std::string_view pFunction) const + { + return Builder(pFunction, detail::TypeId<_recordType>::get()); + } -namespace rtl { - namespace builder +/* @method: methodConst<...>() + @param: std::string, name of function as string. + @return: Builder + * registers const member functions. + * used for registering overloads, for unique member function, use non-templated version 'methodConst()'. + * template parameters must be explicitly specified, should be exactly same as the member-function being registered. + * the 'build(..)' called on return object will accepts const member-function-pointer only. + * compiler error on 'build(..)' if non-const, static member or non-member function pointer is passed. +*/ template + template + inline const Builder MethodBuilder<_recordType>::methodConst(const std::string_view pFunction) const { - template - inline RecordBuilder<_recordType>::RecordBuilder(const std::string& pNamespace, const std::string& pRecord) - : m_record(pRecord) - , m_namespace(pNamespace) { - } - - - /* @method: methodStatic() - @param: std::string, name of function as string. - @return: Builder - * registers only static member functions. - * used for registering unique static member function, if overload exists, use templated version 'methodStatic<...>()'. - * the 'build(..)' called on return object will accepts static member function pointer only. - * compiler error on 'build(..)' if non-static member or non-member function pointer is passed. - */ template - inline constexpr const Builder RecordBuilder<_recordType>::methodStatic(const std::string& pFunction) const - { - return Builder(m_namespace, m_record, pFunction); - } - - - /* @method: methodStatic<...>() - @param: std::string, name of function as string. - @return: Builder - * registers only static member functions. - * used for registering overloads, if unique member function, use non-templated version 'methodStatic()'. - * template parameters must be explicitly specified, should be exactly same as the member-function being registered. - * the 'build(..)' called on return object will accepts static member function pointer only. - * compiler error on 'build(..)' if const member or non-member function pointer is passed. - */ template - template - inline constexpr const Builder RecordBuilder<_recordType>::methodStatic(const std::string& pFunction) const - { - return Builder(m_namespace, m_record, pFunction); - } - - - /* @method: method() - @param: std::string, name of function as string. - @return: Builder - * registers non-const, non-static member functions. - * the 'build(..)' called on return object will accepts non-const, non-static member-function-pointer only. - * compiler error on 'build(..)' if const, static member or non-member function pointer is passed. - */ template - inline constexpr const Builder RecordBuilder<_recordType>::method(const std::string& pFunction) const - { - return Builder(m_namespace, m_record, pFunction); - } - - - /* @method: methodConst() - @param: std::string, name of function as string. - @return: Builder - * registers const member functions. - * used for registering unique member function, if overload exists, use templated version 'methodConst<...>()'. - * template parameters must be explicitly specified, should be exactly same as the member-function being registered. - * the 'build(..)' called on return object will accepts non-const member-function-pointer only. - * compiler error 'build(..)' if non-const, static member or non-member function pointer is passed. - */ template - inline constexpr const Builder RecordBuilder<_recordType>::methodConst(const std::string& pFunction) const - { - return Builder(m_namespace, m_record, pFunction); - } - - - /* @method: method() - @param: std::string, name of function as string. - @return: Builder - * registers non-const member functions. - * used for registering overloads, for unique member function, use non-templated version 'method()'. - * template parameters must be explicitly specified, should be exactly same as the member-function being registered. - * the 'build(..)' called on return object will accepts non-const member-function-pointer only. - * compiler error on 'build(..)' if const, static member or non-member function pointer is passed. - */ template - template - inline constexpr const Builder RecordBuilder<_recordType>::method(const std::string& pFunction) const - { - return Builder(m_namespace, m_record, pFunction); - } - - - /* @method: methodConst<...>() - @param: std::string, name of function as string. - @return: Builder - * registers const member functions. - * used for registering overloads, for unique member function, use non-templated version 'methodConst()'. - * template parameters must be explicitly specified, should be exactly same as the member-function being registered. - * the 'build(..)' called on return object will accepts const member-function-pointer only. - * compiler error on 'build(..)' if non-const, static member or non-member function pointer is passed. - */ template - template - inline constexpr const Builder RecordBuilder<_recordType>::methodConst(const std::string& pFunction) const - { - return Builder(m_namespace, m_record, pFunction); - } - - - /* @method: constructor<...>() - @param: none - @return: ConstructorBuilder<_recordType, _signature...> - * the copy constructors params are detected at compile time only. - * template params <...> - any combination of parameters. - */ template - template - inline constexpr const ConstructorBuilder<_recordType, _signature...> RecordBuilder<_recordType>::constructor() const - { - //this code-block is retained by compiler, if copy constructor with non-const ref('_recordType&') is being registered. - if constexpr (std::is_same_v<_recordType&, typename detail::TypeId<_signature...>::HEAD>) - { - return ConstructorBuilder<_recordType, _signature...>(m_namespace, m_record, FunctorType::CopyCtor); - } - //this code-block is retained by compiler, if copy constructor with const-ref('const _recordType&') is being registered. - else if constexpr (std::is_same_v::HEAD>) - { - return ConstructorBuilder<_recordType, _signature...>(m_namespace, m_record, FunctorType::CopyCtorConst); - } - //if any other constructor except, copy constructor is being registered, this code-block will be retained. - else - { - return ConstructorBuilder<_recordType, _signature...>(m_namespace, m_record, FunctorType::Ctor); - } - } + return Builder(pFunction, detail::TypeId<_recordType>::get()); } } \ No newline at end of file diff --git a/ReflectionTemplateLib/builder/inc/Reflect.h b/ReflectionTemplateLib/builder/inc/Reflect.h index 341ca0c4..e187d34b 100644 --- a/ReflectionTemplateLib/builder/inc/Reflect.h +++ b/ReflectionTemplateLib/builder/inc/Reflect.h @@ -1,39 +1,86 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include #include "Constants.h" #include "Builder.h" -namespace rtl { +namespace rtl::builder +{ + template + class RecordBuilder; + + template + class MethodBuilder; +} + - namespace builder +namespace rtl +{ +/* @class: Reflect + * provides interface to register all kinds of functions (member/non-member). +*/ struct type_ns { + type_ns() = delete; + type_ns(type_ns&&) = delete; + type_ns(const type_ns&) = delete; + type_ns& operator=(type_ns&&) = delete; + type_ns& operator=(const type_ns&) = delete; + + type_ns(const std::string_view pNamespace); + template - class RecordBuilder; + constexpr const builder::RecordBuilder<_recordType> record(const std::string_view pClass); + + template + constexpr const builder::Builder function(const std::string_view pFunction); - /* @class: Reflect - * provides interface to register all kinds of functions (member/non-member). - */ class Reflect - { - //name of the class, struct being registered. - std::string m_record; + private: - //name of the namespace being registered. - std::string m_namespace; + //name of the class, struct being registered. + std::string_view m_record; - public: + //name of the namespace being registered. + std::string_view m_namespace; + }; - Reflect(); - Reflect(const Reflect&) = delete; - Reflect& operator=(const Reflect&) = delete; - Reflect& nameSpace(const std::string& pNamespace); - template - constexpr const Builder function(const std::string& pFunction); +/* @class: Reflect + * provides interface to register all kinds of functions (member/non-member). +*/ struct type + { + type() = default; + type(type&&) = delete; + type(const type&) = delete; + type& operator=(type&&) = delete; + type& operator=(const type&) = delete; + + type_ns ns(const std::string_view pNamespace = detail::NAMESPACE_GLOBAL); + + template + constexpr const builder::MethodBuilder<_recordType> member() { + return builder::MethodBuilder<_recordType>(); + } + + template + constexpr const builder::RecordBuilder<_recordType> record(const std::string_view pClass) { + return ns().record<_recordType>(pClass); + } - template - constexpr const RecordBuilder<_recordType> record(const std::string& pClass); - }; - } + template + constexpr const builder::Builder function(const std::string_view pFunction) { + return ns().function<_signature...>(pFunction); + } + }; } \ No newline at end of file diff --git a/ReflectionTemplateLib/builder/inc/Reflect.hpp b/ReflectionTemplateLib/builder/inc/Reflect.hpp index d1147033..07cda31d 100644 --- a/ReflectionTemplateLib/builder/inc/Reflect.hpp +++ b/ReflectionTemplateLib/builder/inc/Reflect.hpp @@ -1,74 +1,80 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include "Reflect.h" #include "Builder.hpp" #include "RecordBuilder.hpp" -namespace rtl { - - namespace builder - { - inline Reflect::Reflect() - : m_record("") - //If no namespace is given, types are kept under default name: NAMESPACE_GLOBAL. - , m_namespace(NAMESPACE_GLOBAL) { - } +namespace rtl +{ + inline type_ns::type_ns(const std::string_view pNamespace) + : m_record("") + , m_namespace(pNamespace) + { } - /* @function: nameSpace() - @param: std::string, name of the 'namespace' as string. - @return: '*this', Reflect. - * used to group registered function, class/struct under a namespace name. - * its an internal grouping of registered types under a 'namespace' name. - * providing a namespace is optional. registration can be done without a namespace name, even if a type exists in one. - * if types are registered with 'namespace' name, then it must be passed when retriving the objects from 'CxxMirror', - check functions, CxxMirror::getFunction("name_space", "func_name") & CxxMirror::getRecord("name_space","class_name"), - if no namespace is given, then CxxMirror::getFunction("func_name") & CxxMirror::getRecord("class_name") - */ inline Reflect& Reflect::nameSpace(const std::string& pNamespace) - { - m_namespace = pNamespace; - return *this; - } +/* @function: ns() + @param: std::string, name of the 'namespace' as string. + @return: '*this', Reflect. + * used to group registered function, class/struct under a namespace name. + * its an internal grouping of registered types under a 'namespace' name. + * providing a namespace is optional. registration can be done without a namespace name, even if a type exists in one. + * if types are registered with 'namespace' name, then it must be passed when retriving the objects from 'CxxMirror', + check functions, CxxMirror::getFunction("name_space", "func_name") & CxxMirror::getRecord("name_space","class_name"), + if no namespace is given, then CxxMirror::getFunction("func_name") & CxxMirror::getRecord("class_name") +*/ inline type_ns type::ns(const std::string_view pNamespace /* = detail::NAMESPACE_GLOBAL*/) + { + return type_ns(pNamespace); + } - /* @function: function() - @param: std::string (name of the function). - @return: Builder - * registers only non-member functions. - * the 'build(..)' called on return object accepts non-member function pointer only. - * compiler error on 'build(..)' if member function pointer is passed. - */ template<> - inline const Builder Reflect::function(const std::string& pFunction) - { - return Builder(m_namespace, m_record, pFunction); - } +/* @function: function() + @param: std::string (name of the function). + @return: Builder + * registers only non-member functions. + * the 'build(..)' called on return object accepts non-member function pointer only. + * compiler error on 'build(..)' if member function pointer is passed. +*/ template<> + inline const builder::Builder type_ns::function(const std::string_view pFunction) + { + return builder::Builder(detail::TypeId<>::None, pFunction, m_namespace); + } - /* @function: record() - @param: std::string (name of class/struct) - @return: RecordBuilder<_recordType> - * provides object of 'RecordBuilder', which provides interface to registers member functions of class/struct of '_recordType'. - * the 'build(..)' called on return object accepts non-member function pointer only. - * compiler error on 'build(..)' if function pointer passed is not a member of class/struct- '_recordType'. - */ template - inline constexpr const RecordBuilder<_recordType> Reflect::record(const std::string& pClass) - { - return RecordBuilder<_recordType>(m_namespace, pClass); - } +/* @function: record() + @param: std::string (name of class/struct) + @return: RecordBuilder<_recordType> + * provides object of 'RecordBuilder', which provides interface to registers member functions of class/struct of '_recordType'. + * the 'build(..)' called on return object accepts non-member function pointer only. + * compiler error on 'build(..)' if function pointer passed is not a member of class/struct- '_recordType'. +*/ template + inline constexpr const builder::RecordBuilder<_recordType> type_ns::record(const std::string_view pClass) + { + return builder::RecordBuilder<_recordType>(m_namespace, pClass, detail::TypeId<_recordType>::get()); + } - /* @method: function<...>() - @param: std::string (name of function) - @return: Builder - * registers only non-member functions. - * used for registering overloads, if unique member function, use non-templated version 'function()'. - * template parameters must be explicitly specified, should be exactly same as the function being registered. - * the 'build(..)' called on return object accepts non-member function pointer only. - * compiler error on 'build(..)' if any member function pointer is passed. - */ template - inline constexpr const Builder Reflect::function(const std::string& pFunction) - { - return Builder(m_namespace, m_record, pFunction); - } +/* @method: function<...>() + @param: std::string (name of function) + @return: Builder + * registers only non-member functions. + * used for registering overloads, if unique member function, use non-templated version 'function()'. + * template parameters must be explicitly specified, should be exactly same as the function being registered. + * the 'build(..)' called on return object accepts non-member function pointer only. + * compiler error on 'build(..)' if any member function pointer is passed. +*/ template + inline constexpr const builder::Builder type_ns::function(const std::string_view pFunction) + { + return builder::Builder(detail::TypeId<>::None, pFunction, m_namespace); } } \ No newline at end of file diff --git a/ReflectionTemplateLib/common/Constants.h b/ReflectionTemplateLib/common/Constants.h index 3ae4fc51..d595fdc0 100644 --- a/ReflectionTemplateLib/common/Constants.h +++ b/ReflectionTemplateLib/common/Constants.h @@ -1,74 +1,174 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once -#include -#include +#include "error_codes.h" namespace rtl { - //Qualifier type. - enum class TypeQ + // Allocation policy for rtl::RObject. + // + // Determines how the underlying object is created and managed at runtime. + // RTL enforces strict RAII semantics: no explicit destroy API exists, and + // cleanup is always automatic. + enum class alloc { - None, - Mute, //Mutable - Const, //Constant + None = 0,/* + * Assigned to empty or moved-from RObjects. + * - Represents an invalid / non-owning state. + * - Any attempt to call or clone results in rtl::error::EmptyRObject. + */ + + Heap, /* + * Assigned to RTL-allocated heap objects. + * - Internally managed via std::unique_ptr. + * - Moving transfers ownership of the unique_ptr (cheap, no deep copy). + * - Destroyed automatically when the owning RObject goes out of scope. + * - User never sees the unique_ptr; wrapper details remain hidden. + */ + + Stack /* + * Assigned to return values and RTL-allocated stack objects. + * - The object instance lives directly inside the RObject. + * - Moving calls the reflected type's move constructor. + * - Destroyed automatically at scope exit (like any local variable). + */ }; - //Qualifier type. - enum class FunctorType + + // Cloning policy for rtl::RObject. + enum class copy { - None, - Ctor, - CopyCtor, - CopyCtorConst, - DCtor, - Static, - Method, - Function + /* + * An rtl::RObject may internally hold values wrapped in std::optional, + * std::reference_wrapper, or smart pointers. The copy policy gives users + * control over whether cloning should duplicate the wrapper itself or + * perform a deep copy of the underlying object. + * + * Auto (default): + * - RTL first attempts a wrapper-level copy if the wrapper is copyable. + * - If the wrapper is an internal detail (e.g., heap objects stored in + * std::unique_ptr), RTL transparently performs a deep copy of the + * underlying object instead of copying the wrapper. + * - This ensures correct semantics even when the user is unaware of + * wrapper details (typical in reflection use cases). + * - When explicitly requested, users can still attempt Value or Wrapper + * cloning; RTL will return success or a detailed error as appropriate. + * + * Value: + * - Always perform an independent deep copy of the underlying object. + * + * Wrapper: + * - Copy the wrapper itself, without cloning the underlying object. + */ + Auto, + Value, + Wrapper }; - enum class Error + // Utility wrapper for const-correctness control in overload resolution. + // + // Used to explicitly request that RTL treat an rtl::RObject as non-const + // when invoking member functions. Mirrors the intent of const_cast in C++, + // but with provenance-aware safety: it works only if the object was not + // originally declared const. + template + struct constCast { - None, - EmptyInstance, - SignatureMismatch, - InstanceTypeMismatch, - InstanceConstMismatch, - ConstructorNotFound, - CopyConstructorNotFound, - ConstCopyConstructorNotFound + const T& m_target; + + constCast() = delete; + constCast(constCast&&) = delete; + constCast(const constCast&) = delete; + + explicit constCast(const T& target) : m_target(target) {} }; - constexpr const char* NAMESPACE_GLOBAL = "namespace_global"; + // Invalid number/index. + static constexpr std::size_t index_none = static_cast(-1); +} + + + +namespace rtl::detail +{ + enum class EntityKind + { + None = 0, + Ptr, + Value, + Wrapper + }; - struct CtorName + enum class Wrapper { - static const std::string ctor(const std::string& pRecordName) { - return (pRecordName + "::" + pRecordName + "()"); - } + None = 0, + Any, + Weak, + Unique, + Shared, //Planned. + Variant, //Planned. + Optional, //Planned. + Reference //Planned. + }; - static const std::string dctor(const std::string& pRecordName) { - return (pRecordName + "::~" + pRecordName + "()"); - } + enum Index + { + Ctor = 0, + CopyCtor + }; - static const std::string copy(const std::string& pRecordName) { - return (pRecordName + "::" + pRecordName + "(" + pRecordName + "&)"); - } - static const std::string constCopy(const std::string& pRecordName) { - return (pRecordName + "::" + pRecordName + "(const " + pRecordName + "&)"); - } + // MethodQ: Method qualifier + static marker. + enum class methodQ + { + None = 0, // Static method (no const/non-const qualifier) + Const, // Const-qualified instance method + NonConst // Non-const instance method }; + constexpr const std::string_view NAMESPACE_GLOBAL = "global"; + + inline static const std::string ctor_name(const std::string_view pRecordName = "") { + // [critical] Must not change. Constructors are identified using this format. + return (std::string(pRecordName) + "::" + std::string(pRecordName) + "()"); + } #define GETTER(_varType, _name, _var) \ inline constexpr const _varType& get##_name() const { \ return _var; \ } - #define GETTER_REF(_varType, _name, _var) \ inline _varType& get##_name() const { \ return _var; \ } + +#define GETTER_CREF(_varType, _name, _var) \ + inline const _varType& get##_name() const { \ + return _var; \ + } + +#define GETTER_BOOL(_name, _var) \ + inline const bool is##_name() const { \ + return _var; \ + } + +#if defined(_MSC_VER) +#define FORCE_INLINE __forceinline +#elif defined(__GNUC__) || defined(__clang__) +#define FORCE_INLINE inline __attribute__((always_inline)) +#else +#define FORCE_INLINE inline +#endif } \ No newline at end of file diff --git a/ReflectionTemplateLib/common/ConversionUtils.h b/ReflectionTemplateLib/common/ConversionUtils.h new file mode 100644 index 00000000..19002bd2 --- /dev/null +++ b/ReflectionTemplateLib/common/ConversionUtils.h @@ -0,0 +1,88 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include +#include +#include + +namespace rtl::traits { + + template + constexpr bool is_safe_conversion_v = [] { + // 1. Same type check (ignoring cv-qualifiers) + using NakedFrom = std::remove_cv_t; + using NakedTo = std::remove_cv_t; + + if constexpr (std::is_same_v) + return true; + + // 2. Boolean conversion handling + if constexpr (std::is_same_v) + return std::is_arithmetic_v; + if constexpr (std::is_same_v) + return std::is_arithmetic_v; + + // 3. Character type safety + constexpr bool from_char = std::is_same_v || + std::is_same_v || + std::is_same_v; + constexpr bool to_char = std::is_same_v || + std::is_same_v || + std::is_same_v; + + if constexpr (from_char || to_char) { + // Block sign changes between char types + if constexpr ((std::is_same_v && std::is_same_v) || + (std::is_same_v && std::is_same_v)) + return false; + + // Allow same-sign conversions between char types + if constexpr (from_char && to_char) + return (std::is_signed_v == std::is_signed_v); + + // For numeric->char, require size safety + if constexpr (to_char) + return sizeof(NakedFrom) < sizeof(NakedTo); + } + + // 4. Require both types to be arithmetic + if constexpr (!std::is_arithmetic_v || !std::is_arithmetic_v) + return false; + + // 5. Numeric conversion safety + // Floating-point to integer: never safe (truncation) + if constexpr (std::is_floating_point_v && std::is_integral_v) + return false; + + // Integer to floating-point: check mantissa precision + if constexpr (std::is_integral_v && std::is_floating_point_v) + return std::numeric_limits::digits >= std::numeric_limits::digits; + + // Floating-point to floating-point: check both digits and exponent + if constexpr (std::is_floating_point_v && std::is_floating_point_v) + return std::numeric_limits::digits >= std::numeric_limits::digits && + std::numeric_limits::max_exponent >= std::numeric_limits::max_exponent; + + // Integer to integer: + if constexpr (std::is_integral_v && std::is_integral_v) { + // Different signedness requires larger destination + if constexpr (std::is_signed_v != std::is_signed_v) + return sizeof(NakedTo) > sizeof(NakedFrom); + + // Same signedness requires equal or larger size + return sizeof(NakedTo) >= sizeof(NakedFrom); + } + + return false; + }(); +} \ No newline at end of file diff --git a/ReflectionTemplateLib/common/RTLibInterface.h b/ReflectionTemplateLib/common/RTLibInterface.h index 9a03c56a..2f1cdd4d 100644 --- a/ReflectionTemplateLib/common/RTLibInterface.h +++ b/ReflectionTemplateLib/common/RTLibInterface.h @@ -1,75 +1,104 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once + /* -* Provides interface to register all types. -* Reflect().nameSpace("namespaceName").function<>("funcName").build(funcPtr) -* Reflect().nameSpace("namespaceName").record("className").constructor<>("methodName").build() -* Reflect().nameSpace("namespaceName").record("className").method<>("methodName").build(&MethodPtr) -* -* template params are for overloading the different signatures (of method/function/constructors). -* if the function/method is unique, no need to specify the signature as templete params. if they -* are overloaded and any one of them takes zero params, then that function must be registered by specifying as -* template parameter. Constructor overloads do not need to specify as tempelate params even if other overload exists. -* decleared in namespace rtl::builder. */ +* Provides the interface to register types and functions with RTL. +* +* Example usage: +* rtl::type().ns("ns").function("func").build(&func); +* rtl::type().ns("ns").record("MyClass").build(); +* rtl::type().member().constructor().build(); +* rtl::type().member().method("setName").build(&MyClass::setName); +* +* Template parameters are required only for overload resolution: +* - If the function/method is unique, template parameters are optional. +* - If overloads exist and one of them has zero parameters, that overload +* must be registered with . +* - Constructor overloads never require , even if a zero-argument +* constructor exists. +* +* Declared in namespace rtl::builder. +*/ #include "Reflect.hpp" /* -* Interface to access user defined class/struct(s) and its members(veariables, functions, constructor & destructor). -* it encapsulates all the member's information and provides objects (Function/Method) to access them. -* the Record objects are obtained from reflection object ie, CxxMirror, querying by string. -* decleared in namespace rtl::access.*/ -#include "Record.hpp" +* Interface for accessing user-defined classes/structs and their members +* (constructors, methods, and fields). +* +* A Record encapsulates all metadata for a reflected type and provides +* objects (Method, Function) to access its members. +* +* Record instances are retrieved from the global reflection mirror: +* std::optional rec = cxx::mirror().getRecord("MyClass"); +* +* Declared in namespace rtl. +*/ +#include "Record.h" /* -* Provides interface to call global functions (may or not be in a namespace), static member functions of class/struct(s). -* it overloads "operator()". can be called as functionObj(..args..), where functionObj is object of "class Function" -* the global Function objects can be directly obtained from reflection object ie, CxxMirror, querying by string. -* decleared in namespace rtl::access.*/ +* Provides the interface for invoking global functions (optionally within +* a namespace) and static member functions of classes/structs. +* +* The class overloads operator(), allowing direct invocation: +* auto [err, ret] = funcObj.bind().call(arg1, arg2); +* +* Global Function objects are obtained from the reflection mirror: +* std::optional func = cxx::mirror().getFunction("ns", "funcName"); +* +* Declared in namespace rtl. +*/ #include "Function.hpp" -/* -* Provides interface to call methods on objects created via reflection of classes/structs. -* it also overloads "operator()", but this takes the object (type 'Instance') instead of the method arguments and returns -* the obeject of class 'MethodInvoker, which provides 'invoke' function to finally call the method with arguments. -* -* Difference between Method & Function class: -* - They both overload the operator(), but when calling via "Function" object, it takes parameters to be passed. -* - When calling via "Method", it takes target object on which the reflected method needs to be called and then provides -* interface 'invoke()' on the return value, which takes the actual parameters. So, -* Function call: function(..args..); -* Method call: method(targetObj).invoke(..args..); -* -* decleared in namespace rtl::access. */ +/* +* Provides the interface for invoking member functions on reflected objects. +* +* Like Function, it overloads operator(), but instead of taking arguments +* directly, it first binds a target object and then allows calling with +* invoke(..args..). +* +* Example usage: +* auto [err, ret] = methodObj.bind(targetObj).call(arg1, arg2); +* +* Difference between Function and Method: +* - Function: bind() -> call(..args..) +* Example -> funcObj.bind().call(..args..); +* +* - Method: bind(targetObj) -> call(..args..) +* Example -> methodObj.bind(targetObj).call(..args..); +* +* Declared in namespace rtl. +*/ #include "Method.hpp" /* -* RStatus, Provides interface to check if the call succeeded and to access the return values obtained -* from calling methods/functions/constructors if any. It contains object of Instance, which may or may not have the return value. -* Instance hold resource, it is owned by RStatus, once 'reteaseReturn()' is called on RStatus, it will relieve itseld from the ownership. -* -* 'Instance' is a wrapper class for std::any, which adds interface to perform exception-safe non-rtti type check. and -* calls the destructor when goes out of scope, only for the objects created by calling instance() method on Record objects, -* ie, the destructor will only be called for the objects that are created via reflection on the heap. It will not be called for -* the objects recieved as return vales from reflected method/function call. -* - supports only move semantics. -* Interfaces: -* - get(), provides the std::any object, which can be checked using has_value() if it contains any object. -* - isOfType<_type>(), checks of the underlying object is of '_type'. -* - finally, std::any_cast<_type>() can be used to obtain the actual object with '_type' -* For example, a function returns value as 'std::string', but calling it via reflection will return the 'Instance' -* object (suppose, retObj). it must be validated before finally applying the std::any_cast<>() to avoid exception, like, -* 1. if(retObj.get().has_value() == true) -* 2. if(retObj.isOfType() == true) -* 3. std::string str = std::any_cast(retObj.get()) -* -* decleared in namespace rtl::access. */ -#include "RStatus.h" - - -/* Class containing everything required to provide reflection interface and functionality. -* Users are required to instantiate this class and pass all registration as constructor parameter. */ -#include "CxxMirror.h" \ No newline at end of file +* The root reflection container that aggregates all registrations. +* Users are expected to define a singleton CxxMirror that holds all +* records and functions: +* +* namespace cxx { +* const rtl::CxxMirror& mirror() { +* static rtl::CxxMirror m = rtl::CxxMirror({ +* // registrations here... +* }); +* return m; +* } +* } +* +* Declared in namespace rtl. +*/ +#include "CxxMirror.hpp" \ No newline at end of file diff --git a/ReflectionTemplateLib/common/error_codes.h b/ReflectionTemplateLib/common/error_codes.h new file mode 100644 index 00000000..4dc5e28c --- /dev/null +++ b/ReflectionTemplateLib/common/error_codes.h @@ -0,0 +1,71 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include + +namespace rtl +{ + enum class error + { + None, + EmptyRObject, + NotWrapperType, + + TargetMismatch, + SignatureMismatch, + CloningDisabled, //Used only in case of cloning is disabled e.g, unregistered type. + FunctionNotRegistered, //Not used by RTL at all, for external purpose only. + + IllegalConstCast, + ConstOverloadMissing, + NonConstOverloadMissing, + + TypeNotCopyConstructible, + TypeNotDefaultConstructible, + + StlWrapperHeapAllocForbidden, + }; + + + inline const std::string_view to_string(error err) + { + switch (err) { + case error::None: + return "No error (operation successful)"; + case error::EmptyRObject: + return "Empty instance: RObject does not hold any reflected object"; + case error::SignatureMismatch: + return "Signature mismatch: Function parameters do not match the expected signature"; + case error::CloningDisabled: + return "Type not registered: The requested type is not explicitly registered in the Reflection system"; + case error::FunctionNotRegistered: + return "Function not registered: The requested function/method is not registered in the Reflection system"; + case error::TargetMismatch: + return "The object you're trying to bind doesn't match the expected type of the method."; + case error::NonConstOverloadMissing: + return "Non-const method not found: The method does not have a non-const overload as explicitly requested."; + case error::TypeNotCopyConstructible: + return "Copy constructor inaccessible: Underlying type has deleted or private copy constructor; cannot copy-construct reflected instance"; + case error::TypeNotDefaultConstructible: + return "Type cannot be default constructed - std::is_default_constructible validation failed"; + case error::ConstOverloadMissing: + return "Cannot call non-const method on const target implicitly, bind methodQ::NonConst to override."; + case error::IllegalConstCast: + return "Illegal const_cast attempt - cannot remove const qualifier from originally-const object"; + case error::StlWrapperHeapAllocForbidden: + return "Heap allocation forbidden for STL-wrapped objects (smart pointers/optionals/reference_wrappers). use alloc::Stack."; + default: + return "Unknown error"; + } + } +} \ No newline at end of file diff --git a/ReflectionTemplateLib/common/rtl_debug.hpp b/ReflectionTemplateLib/common/rtl_debug.hpp new file mode 100644 index 00000000..4d2b4739 --- /dev/null +++ b/ReflectionTemplateLib/common/rtl_debug.hpp @@ -0,0 +1,27 @@ +#pragma once +// #include + +#ifdef RTL_DEBUG + +// Runs arbitrary code safely in Debug builds (single-statement safe) +#define RTL_DEBUG_ONLY(code) do { code } while(0) + +// Simple debug log +// #define RTL_LOG(msg) do { std::cout << "[RTL-DEBUG] " << msg << '\n'; } while(0) + +// Exception-free assert: if condition fails, prints message and returns error code +// #define RTL_ASSERT(cond, err_code) do { \ +// if (!(cond)) { \ +// std::cerr << "[RTL-ASSERT] " #cond " failed!\n"; \ +// return err_code; \ +// } \ +// } while(0) + +#else + +// Release builds: completely stripped out +#define RTL_DEBUG_ONLY(code) do {} while(0) +// #define RTL_LOG(msg) do {} while(0) +// #define RTL_ASSERT(cond, err_code) do {} while(0) + +#endif diff --git a/ReflectionTemplateLib/common/rtl_traits.h b/ReflectionTemplateLib/common/rtl_traits.h new file mode 100644 index 00000000..323c6af3 --- /dev/null +++ b/ReflectionTemplateLib/common/rtl_traits.h @@ -0,0 +1,154 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "TypeId.h" +#include "Constants.h" + +namespace rtl +{ + class RObject; + + namespace detail { + + template + class FunctorContainer; + } + + namespace traits + { + using Converter = std::function< std::any(const std::any&, const detail::EntityKind&, detail::EntityKind&) >; + + using ConverterPair = std::pair< std::size_t, Converter >; + + using Cloner = detail::FunctorContainer; + } + + namespace traits + { + template + struct raw_type { + using type = std::remove_cv_t>>>; + }; + + template + using raw_t = typename raw_type::type; + + // Utility: Remove const and reference qualifiers from T. + template + using remove_const_n_ref_t = std::remove_const_t>; + + // Utility: Remove const from T if T is not a reference; otherwise, leave as is. + template + using remove_const_if_not_reference = std::conditional_t< std::is_reference_v, T, std::remove_const_t>; + + // Utility: Remove const, reference, and pointer from T (after decay). + template + using remove_const_n_ref_n_ptr = std::remove_const_t>>>; + + template + inline constexpr bool is_raw_ptr_v = std::is_pointer_v>; + + template + inline constexpr bool is_const_v = (std::is_const_v> || (std::is_pointer_v && std::is_const_v>)); + + template + inline constexpr bool is_first_type_same_v = std::is_same_v::HEAD>, raw_t<_checkType>>; + } + + + namespace traits + { + template + struct std_wrapper + { + using value_type = std::nullptr_t; + static constexpr const auto type = detail::Wrapper::None; + static constexpr std::size_t id() { return detail::TypeId<>::None; } + }; + + + template + struct std_wrapper> + { + using value_type = T; + static constexpr const auto type = detail::Wrapper::Shared; + static constexpr std::size_t id() { return detail::TypeId>::get(); } + }; + + + template + struct std_wrapper> + { + using value_type = T; + static constexpr const auto type = detail::Wrapper::Unique; + static constexpr std::size_t id() { return detail::TypeId>::get(); } + }; + + + template + struct std_wrapper> + { + using value_type = T; + static constexpr const auto type = detail::Wrapper::Weak; + static constexpr std::size_t id() { return detail::TypeId>::get(); } + }; + + template + constexpr auto wrapper_type_v = std_wrapper::type; + + template + constexpr bool is_weak_ptr_v = (wrapper_type_v == detail::Wrapper::Weak); + + template + constexpr bool is_unique_ptr_v = (wrapper_type_v == detail::Wrapper::Unique); + + template + constexpr bool is_shared_ptr_v = (wrapper_type_v == detail::Wrapper::Shared); + + template + constexpr bool is_not_any_wrapper_v = (wrapper_type_v == detail::Wrapper::None); + + template + using enable_if_unique_ptr = std::enable_if::type == detail::Wrapper::Unique, int>::type; + + template + using enable_if_shared_ptr = std::enable_if::type == detail::Wrapper::Shared, int>::type; + } + + + namespace traits + { + template + concept has_constructor = requires(Args&&... args) { + T{ std::forward(args)... }; + }; + + template + constexpr bool is_bare_type() + { + static_assert(!std::is_const_v, "Provide bare type (remove const)."); + static_assert(!std::is_pointer_v, "Provide bare type (remove pointer)."); + static_assert(!std::is_reference_v, "Provide bare type (remove reference)."); + + return !(std::is_const_v || std::is_pointer_v || std::is_reference_v); + } + } +} \ No newline at end of file diff --git a/ReflectionTemplateLib/common/view.h b/ReflectionTemplateLib/common/view.h new file mode 100644 index 00000000..5e607165 --- /dev/null +++ b/ReflectionTemplateLib/common/view.h @@ -0,0 +1,130 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +/** + * @brief A lightweight immutable view of a const T object. + * + * rtl::view provides uniform access to either: + * - a non-owning const reference (borrowed), or + * - an internally stored const value (owned). + * + * Clients should treat this as a non-owning view: the semantics + * are always read-only, and ownership is abstracted away. + * ---------------------------------------------------------------------------- + * Purpose: + * rtl::view is specifically designed to provide read-only access to values + * reflected by an RObject. It abstracts whether the value is owned or + * referenced, allowing seamless access in both cases. + * + * Lifetime: + * A rtl::view instance is only valid as long as the associated RObject + * from which it was obtained remains alive. If the RObject is destroyed, + * any rtl::view referencing its data becomes invalid and must not be used. + * ---------------------------------------------------------------------------- + */ + + +#include +#include +#include "rtl_traits.h" + +namespace rtl { + + template + class view; +} + + +namespace rtl +{ + template + class view<_asType, std::enable_if_t || + traits::is_shared_ptr_v<_asType>> > + { + _asType& m_ref; + + public: + + // Construct from reference (no copy, no default init) + view(_asType& pRef) : m_ref(pRef) {} + + // Delete all forms of copying and moving, enforcing true immutablilty. + view(view&&) = delete; + view(const view&) = delete; + view& operator=(view&&) = delete; + view& operator=(const view&) = delete; + + const _asType& get() const { + return m_ref; + } + }; +} + + +namespace rtl +{ + template + class view<_asType, std::enable_if_t> > + { + const _asType& m_cref; + + public: + + // Construct from reference (no copy, no default init) + view(const _asType& ref) : m_cref(ref) {} + + // Delete all forms of copying and moving, enforcing true immutablilty. + view(view&&) = delete; + view(const view&) = delete; + view& operator=(view&&) = delete; + view& operator=(const view&) = delete; + + const _asType& get() const { + return m_cref; + } + }; +} + + +namespace rtl +{ + template + class view<_asType, std::enable_if_t && + traits::std_wrapper<_asType>::type == detail::Wrapper::None> > + { + /* only constructed if we own the value. + * order matters: m_value must be declared before m_cref + * because m_cref may bind to m_value during initialization + */ const std::optional<_asType> m_value; + + const _asType& m_cref; + + public: + + // Construct from reference (no copy, no default init) + view(const _asType& ref) : m_value(std::nullopt), m_cref(ref) {} + + // Construct from value (copy or move) + view(_asType&& val) : m_value(std::move(val)), m_cref(*m_value) {} + + // Delete all forms of copying and moving, enforcing true immutablilty. + view(view&&) = delete; + view(const view&) = delete; + view& operator=(view&&) = delete; + view& operator=(const view&) = delete; + + const _asType& get() const { + return m_cref; + } + }; +} \ No newline at end of file diff --git a/ReflectionTemplateLib/common/view.hpp b/ReflectionTemplateLib/common/view.hpp new file mode 100644 index 00000000..fb6d0aa0 --- /dev/null +++ b/ReflectionTemplateLib/common/view.hpp @@ -0,0 +1,65 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include +#include + +#include "RObjectUPtr.h" + +/** + * @brief A lightweight immutable view of a const T object. + * + * rtl::view provides uniform access to either: + * - a non-owning const reference (borrowed), or + * - an internally stored const value (owned). + * + * Clients should treat this as a non-owning view: the semantics + * are always read-only, and ownership is abstracted away. + * ---------------------------------------------------------------------------- + * Purpose: + * rtl::view is specifically designed to provide read-only access to values + * reflected by an RObject. It abstracts whether the value is owned or + * referenced, allowing seamless access in both cases. + * + * Lifetime: + * A rtl::view instance is only valid as long as the associated RObject + * from which it was obtained remains alive. If the RObject is destroyed, + * any rtl::view referencing its data becomes invalid and must not be used. + * ---------------------------------------------------------------------------- + */ + +namespace rtl +{ + template + class view<_asType, std::enable_if_t> > + { + using T = typename traits::std_wrapper<_asType>::value_type; + + const detail::RObjectUPtr& m_uptrRef; + + public: + + // Construct from reference (no copy, no default init) + explicit view(const detail::RObjectUPtr& pUptrRef): m_uptrRef(pUptrRef) { } + + // Delete all forms of copying and moving, enforcing true immutablilty. + view(view&&) = delete; + view(const view&) = delete; + view& operator=(view&&) = delete; + view& operator=(const view&) = delete; + + _asType get() const { + return std::move(m_uptrRef.release()); + } + }; +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/CallReflector.h b/ReflectionTemplateLib/detail/inc/CallReflector.h index 8d5b355a..fad4ecd6 100644 --- a/ReflectionTemplateLib/detail/inc/CallReflector.h +++ b/ReflectionTemplateLib/detail/inc/CallReflector.h @@ -1,46 +1,63 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include +#include "RObject.h" #include "Constants.h" -namespace rtl { +namespace rtl::detail { - namespace access { - //forward decl. - class RStatus; - } - namespace detail +/* @struct: CallReflector + @param: _derivedType (type which inherits this class) + * retrieves the lambda at given index and calls it with the arguments supplied. + * deriving classes are, MethodContainer & FunctorContainer. +*/ template + struct CallReflector { - /* @struct: CallReflector - @param: _derivedType (type which inherits this class) - * retrieves the lambda at given index and calls it with the arguments supplied. - * deriving classes are, MethodContainer & FunctorContainer. - */ template - struct CallReflector + /* @method: forwardCall + @param: pFunctorIndex (index of the lambda), _args...(arguments to be passed to that lambda) + * gets the lambda vector from '_derivedType' and calls the lambda at given index with '_args'. + * this 'forwardCall' is for calling lambda containing non-member-function and static-member-function functors. + */ template + FORCE_INLINE static Return forwardCall(std::size_t pFunctorIndex, _params&&..._args) + { + //'getFunctors()' must be implemented by _derivedType (FunctorContainer). + return _derivedType::getFunctors()[pFunctorIndex](std::forward<_params>(_args)...); + } + + + /* @method: forwardCall + @param: pFunctorIndex (index of the lambda), _args...(arguments to be passed to that lambda) + * gets the lambda vector from '_derivedType' and calls the lambda at given index with '_args'. + * this 'forwardCall' is for calling lambda containing constructors. + */ template + FORCE_INLINE static Return forwardCall(std::size_t pFunctorIndex, rtl::alloc pAllocType, std::size_t pClonerIndex, _params&&..._args) + { + //'getFunctors()' must be implemented by _derivedType (FunctorContainer). + return _derivedType::getFunctors()[pFunctorIndex](pAllocType, pClonerIndex, std::forward<_params>(_args)...); + } + + + /* @method: forwardCall + @param: pFunctorIndex (index of the lambda), _args...(arguments to be passed to that lambda) + * gets the lambda vector from '_derivedType' and calls the lambda at given index with '_args'. + * this 'forwardCall' is for calling lambda containing member-function functors. + */ template + FORCE_INLINE static Return forwardCall(const rtl::RObject& pTarget, std::size_t pFunctorIndex, _params&&..._args) { - /* @method: forwardCall - @param: pFunctorIndex (index of the lambda), _args...(arguments to be passed to that lambda) - * gets the lambda vector from '_derivedType' and calls the lambda at given index with '_args'. - * this 'forwardCall' is for calling lambda containing non-member-function and static-member-function functors. - */ template - static access::RStatus forwardCall(std::size_t pFunctorIndex, _params..._args) - { - //'getFunctors()' must be implemented by _derivedType (FunctorContainer). - return _derivedType::getFunctors().at(pFunctorIndex)(_args...); - } - - - /* @method: forwardCall - @param: pFunctorIndex (index of the lambda), _args...(arguments to be passed to that lambda) - * gets the lambda vector from '_derivedType' and calls the lambda at given index with '_args'. - * this 'forwardCall' is for calling lambda containing member-function functors. - */ template - static access::RStatus forwardCall(const std::any& pTarget, std::size_t pFunctorIndex, _params..._args) - { - //'getMethodFunctors()' is implemented by _derivedType (MethodContainer) - return _derivedType::getMethodFunctors().at(pFunctorIndex)(pTarget, _args...); - } - }; - } + //'getMethodFunctors()' is implemented by _derivedType (MethodContainer) + return _derivedType::getMethodFunctors()[pFunctorIndex](pTarget, std::forward<_params>(_args)...); + } + }; } \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/CxxReflection.h b/ReflectionTemplateLib/detail/inc/CxxReflection.h index 3ef39c6e..df1a1b44 100644 --- a/ReflectionTemplateLib/detail/inc/CxxReflection.h +++ b/ReflectionTemplateLib/detail/inc/CxxReflection.h @@ -1,21 +1,24 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include #include #include -#include "Constants.h" +#include "Record.h" namespace rtl { - namespace access - { - //Forward decls. - class Record; - class Method; - class Function; - } - namespace detail { /* @class: CxxReflection @@ -24,40 +27,51 @@ namespace rtl { * organizes the 'Function' objects by namespace, class/structs. */ class CxxReflection { - using RecordMap = std::unordered_map ; - using MethodMap = std::unordered_map ; - using FunctionMap = std::unordered_map ; + using RecordRef = std::reference_wrapper; + using RecordMap = std::unordered_map ; + using MethodMap = std::unordered_map ; + using FunctionMap = std::unordered_map ; + std::unordered_map m_recordIdMap; //contains 'Record' (class/struct) objects, mapped with given namespace name. - std::unordered_map m_nsRecordsMap; - + std::unordered_map m_recordNamespaceMap; //contains 'Function' (non-member-function) objects, mapped with given namespace name. - std::unordered_map m_nsFunctionsMap; + std::unordered_map m_functionNamespaceMap; - void organizeFunctorsMetaData(const access::Function& pFunction); + void addInNamespaceMap(Record& pRecord); + void buildRecordIdMap(const std::vector& pFunctions); + void insertFunctionToNamespaceMap(const Function& pFunction); + bool insertFunctionToRecordIdMap(const Function& pFunction); - void addRecord(RecordMap& pRecordMap, const access::Function& pFunction); - void addMethod(MethodMap& pMethodMap, const access::Function& pFunction); - void addFunction(FunctionMap& pFunctionMap, const access::Function& pFunction); + static void addMethod(MethodMap& pMethodMap, const Function& pFunction); + static void addFunction(FunctionMap& pFunctionMap, const Function& pFunction); + static const bool validateFunctionByRecordId(const Function& pFunction); protected: - CxxReflection() = delete; - CxxReflection(CxxReflection&) = delete; - CxxReflection& operator=(CxxReflection&) = delete; - - CxxReflection(const std::vector& pFunctions); + CxxReflection(const std::vector& pFunctions); public: + CxxReflection() = delete; + CxxReflection(CxxReflection&&) = default; + CxxReflection(const CxxReflection&) = default; + CxxReflection& operator=(CxxReflection&&) = delete; + CxxReflection& operator=(const CxxReflection&) = delete; + + //returns the complete map of registered methods grouped by namespace, contained in 'Record' (class/struct) objects. + constexpr const std::unordered_map& getRecordIdMap() const { + return m_recordIdMap; + } + //returns the complete map of registered methods grouped by namespace, contained in 'Record' (class/struct) objects. constexpr const std::unordered_map& getNamespaceRecordMap() const { - return m_nsRecordsMap; + return m_recordNamespaceMap; } //returns the complete map of registered functions ('Function' objects) under a namespace. constexpr const std::unordered_map& getNamespaceFunctionsMap() const { - return m_nsFunctionsMap; + return m_functionNamespaceMap; } }; } diff --git a/ReflectionTemplateLib/detail/inc/FunctionCaller.h b/ReflectionTemplateLib/detail/inc/FunctionCaller.h new file mode 100644 index 00000000..fdcfb53b --- /dev/null +++ b/ReflectionTemplateLib/detail/inc/FunctionCaller.h @@ -0,0 +1,42 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include "RObject.h" + +namespace rtl +{ + class Function; +} + +namespace rtl::detail +{ + template + struct FunctionCaller + { + //the function to be called. + const Function* m_function; + + public: + + template + rtl::Return call(_args&&...) const; + + template + constexpr rtl::Return operator()(_args&&...params) const + { + return call(std::forward<_args>(params)...); + } + + friend Function; + }; +} diff --git a/ReflectionTemplateLib/detail/inc/FunctionCaller.hpp b/ReflectionTemplateLib/detail/inc/FunctionCaller.hpp new file mode 100644 index 00000000..a673afd5 --- /dev/null +++ b/ReflectionTemplateLib/detail/inc/FunctionCaller.hpp @@ -0,0 +1,35 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include "RObject.h" +#include "Function.h" +#include "FunctionCaller.h" +#include "FunctorContainer.h" + +namespace rtl::detail +{ + template + template + FORCE_INLINE Return FunctionCaller<_signature...>::call(_args&&...params) const + { + using Container = std::conditional_t...>, + FunctorContainer<_signature...>>; + + std::size_t index = m_function->hasSignatureId(Container::getContainerId()); + if (index != rtl::index_none) [[likely]] { + return Container::template forwardCall<_args...>(index, std::forward<_args>(params)...); + } + return { error::SignatureMismatch, RObject{} }; + } +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/FunctorContainer.h b/ReflectionTemplateLib/detail/inc/FunctorContainer.h index 302665a8..425ad930 100644 --- a/ReflectionTemplateLib/detail/inc/FunctorContainer.h +++ b/ReflectionTemplateLib/detail/inc/FunctorContainer.h @@ -1,14 +1,24 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include -#include #include #include #include "Constants.h" #include "CallReflector.h" -#include "SetupFunction.hpp" -#include "SetupConstructor.hpp" +#include "SetupFunction.h" +#include "SetupConstructor.h" namespace rtl { @@ -16,64 +26,65 @@ namespace rtl { { //forward decl class ReflectionBuilder; - //unique id generator. - extern std::atomic g_containerIdCounter; /* @class: FunctorContainer @param: '_signature...' (combination of any types) - * container class for holding lambda's wrapping functor, constructor/destructor calls of same signatures. + * container class for holding lambda's wrapping functor, constructor calls of same signatures. * maintains a std::vector with static lifetime. */ template class FunctorContainer : public SetupFunction>, public SetupConstructor>, public CallReflector> { - using FunctionLambda = std::function < access::RStatus(_signature...) >; + using FunctionLambda = std::function < Return(_signature...) >; public: //every FunctorContainer<...> will have a unique-id. - static const std::size_t& getContainerId() { - return m_containerId; + FORCE_INLINE static std::size_t getContainerId() { + static const std::size_t containerId = generate_unique_id(); + return containerId; } //get the vector holding lambdas as 'const-ref' - const static std::vector& getFunctors() { - return m_functors; + FORCE_INLINE const static std::vector& getFunctors() { + static std::vector& functorTable = getFunctorTable(); + return functorTable; } //get functor container type(_signature...) as string with given 'returnType'. template - static const std::string getSignatureStr(const bool pIsMember = false) { - const std::string& retStr = TypeId<_returnType>::toString(); - return (retStr + (pIsMember ? "::" : " ") + "(" + TypeId<_signature...>::toString() + ")"); + static std::string getSignatureStr(const bool pIsMember = false) + { + return (TypeId<_returnType>::toString() + (pIsMember ? "::" : " ") + + "(" + TypeId<_signature...>::toString() + ")"); } private: - //holds unique-id - static const std::size_t m_containerId; - //vector holding lambdas - static std::vector m_functors; + static std::vector& getFunctorTable() { + static std::vector functorTable; + return functorTable; + } /* @method: pushBack - @params: pFunctor (lambda containing functor or constructor/destructor call) + @params: pFunctor (lambda containing functor or constructor call) pGetIndex (lambda providing index if the functor is already registered) pUpdate (lambda updating the already registered functors/ctor/d'tor set) @return: index of newly added or already existing lambda in vector 'm_functors'. - */ static const std::size_t pushBack(const FunctionLambda& pFunctor, - std::function pGetIndex, - std::function pUpdate) + */ static std::size_t pushBack(const FunctionLambda& pFunctor, + std::function pGetIndex, + std::function pUpdate) { //critical section, thread safe. static std::mutex mtx; std::lock_guard lock(mtx); std::size_t index = pGetIndex(); - if (index == -1) { - index = m_functors.size(); + if (index == rtl::index_none) { + index = getFunctorTable().size(); pUpdate(index); - m_functors.push_back(pFunctor); + getFunctorTable().push_back(pFunctor); } return index; } @@ -83,11 +94,5 @@ namespace rtl { friend SetupFunction>; friend SetupConstructor>; }; - - template - const std::size_t FunctorContainer<_signature...>::m_containerId = g_containerIdCounter.fetch_add(1); - - template - std::vector::FunctionLambda> FunctorContainer<_signature...>::m_functors; } } \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/FunctorId.h b/ReflectionTemplateLib/detail/inc/FunctorId.h index b1893fde..5fee2669 100644 --- a/ReflectionTemplateLib/detail/inc/FunctorId.h +++ b/ReflectionTemplateLib/detail/inc/FunctorId.h @@ -1,3 +1,14 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include "TypeId.h" @@ -14,49 +25,53 @@ namespace rtl * once table is found, the functor is accessed at index 'm_index', (never fails, noexcept) * 'FunctorId' generated for a each functor is unique, even for overloaded functions. * multiple registartion of same functor will generate same duplicate 'FunctorId'. - */ class FunctorId + */ struct FunctorId { //index of the functor in the functor-table. - const std::size_t m_index; + std::size_t m_index; //return type-id of the functor registered. - const std::size_t m_returnId; + std::size_t m_returnId; //if functor is a member-function, type id of class/struct it belongs to. - const std::size_t m_recordId; + std::size_t m_recordId; //containerId of the functor-table. - const std::size_t m_containerId; + std::size_t m_containerId; //signature of functor as string. platform dependent, may not be very much readable format. - const std::string m_signature; + std::string m_signature; - public: + GETTER(std::size_t, Index, m_index) + GETTER(std::size_t, ReturnId, m_returnId); + GETTER(std::size_t, RecordId, m_recordId); + GETTER(std::size_t, SignatureId, m_containerId) + GETTER(std::string, SignatureStr, m_signature) - FunctorId() - : m_index(-1) - , m_returnId(TypeId<>::None) - , m_recordId(TypeId<>::None) - , m_containerId(TypeId<>::None) - , m_signature("") { + /* @method: getHashCode() + @return: std::size_t (a unique hash-code for a functor) + * 'm_containerId' will be same for functors(non-member) with same signatures. + * for member functions, a functor will have three atrributes + - signature + - whether it is const or non-const + - class/struct type + 'm_containerId' will be same for functors with same above attributes. + * every functor will have a distinct index in the functor-wrapped-lambda-table. + * so, combination of m_containerId & m_index is unique for every functor. + */ std::size_t getHashCode() const + { + return std::stoull(std::to_string(m_containerId) + + std::to_string(m_index) + + std::to_string(m_recordId) + + std::to_string(m_returnId)); } - FunctorId(const std::size_t& pIndex, - const std::size_t& pReturnId, const std::size_t& pRecordId, - const std::size_t& pContainerId, const std::string& pSignature) - : m_index(pIndex) - , m_returnId(pReturnId) - , m_recordId(pRecordId) - , m_containerId(pContainerId) - , m_signature(pSignature) { + const bool operator==(const FunctorId& pOther) const + { + return (m_index == pOther.m_index && m_returnId == pOther.m_returnId && + m_recordId == pOther.m_recordId && m_containerId == pOther.m_containerId && + m_signature == pOther.m_signature); } - - GETTER(std::size_t, Index, m_index) - GETTER(std::size_t, SignatureId, m_containerId) - GETTER(std::string, SignatureStr, m_signature) - - //get a unique hascode representing a functor. - std::size_t getHashCode() const; }; } } \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/MethodContainer.h b/ReflectionTemplateLib/detail/inc/MethodContainer.h index fc7d5c35..3a9ec85a 100644 --- a/ReflectionTemplateLib/detail/inc/MethodContainer.h +++ b/ReflectionTemplateLib/detail/inc/MethodContainer.h @@ -1,169 +1,175 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include -#include #include #include #include "Constants.h" #include "CallReflector.h" -#include "SetupMethod.hpp" +#include "SetupMethod.h" namespace rtl { + class RObject; + namespace detail { //forward decl class ReflectionBuilder; - //unique id generator. - extern std::atomic g_containerIdCounter; - template + template class MethodContainer; - /* @class: MethodContainer + /* @class: MethodContainer @param: '_signature...' (combination of any types) * container class for holding lambda's wrapping non-const-member-function functor calls of same signatures. * maintains a std::vector with static lifetime. */ template - class MethodContainer : public SetupMethod>, - public CallReflector> + class MethodContainer : public SetupMethod>, + public CallReflector> { - using MethodLambda = std::function < access::RStatus(std::any, _signature...) >; + using MethodLambda = std::function < Return (const rtl::RObject&, _signature...) >; public: - //every MethodContainer will have a unique-id. - static const std::size_t& getContainerId() { - return m_containerId; + //every MethodContainer will have a unique-id. + static std::size_t getContainerId() { + //holds unique-id + static const std::size_t containerId = generate_unique_id(); + return containerId; } //get the vector holding lambdas as 'const-ref' - static const std::vector& getMethodFunctors() { - return m_methodPtrs; + FORCE_INLINE static const std::vector& getMethodFunctors() { + static std::vector& functorTable = getFunctorTable(); + return functorTable; } //get container type as string template - static const std::string getSignatureStr() { - return (TypeId<_returnType>::toString() + " " + TypeId<_recordType>::toString() + "::(" + TypeId<_signature...>::toString() + ")"); + static std::string getSignatureStr() + { + return (TypeId<_returnType>::toString() + " " + TypeId<_recordType>::toString() + + "::(" + TypeId<_signature...>::toString() + ")"); } private: - //holds unique-id - static const std::size_t m_containerId; - //vector holding lambdas - static std::vector m_methodPtrs; + static std::vector& getFunctorTable() { + static std::vector functorTable; + return functorTable; + } /* @method: pushBack @params: pFunctor (lambda containing non-const-member-function functor call) pGetIndex (lambda providing index if the functor is already registered) pUpdate (lambda updating the already registered functors set) @return: index of newly added or already existing lambda in vector 'm_methodPtrs'. - */ static const std::size_t pushBack(const MethodLambda& pFunctor, - std::function pGetIndex, - std::function pUpdateIndex) + */ static std::size_t pushBack(const MethodLambda& pFunctor, + std::function pGetIndex, + std::function pUpdateIndex) { //critical section, thread safe. static std::mutex mtx; std::lock_guard lock(mtx); std::size_t index = pGetIndex(); - if (index == -1) { - index = m_methodPtrs.size(); + if (index == rtl::index_none) { + index = getFunctorTable().size(); pUpdateIndex(index); - m_methodPtrs.push_back(pFunctor); + getFunctorTable().push_back(pFunctor); } return index; } //friends :) friend ReflectionBuilder; - friend SetupMethod>; + friend SetupMethod>; }; - - template - const std::size_t MethodContainer::m_containerId = g_containerIdCounter.fetch_add(1); - - template - std::vector::MethodLambda> - MethodContainer::m_methodPtrs; } namespace detail { - /* @class: MethodContainer + /* @class: MethodContainer @param: '_signature...' (combination of any types) * container class for holding lambda's wrapping const-member-function functor calls of same signatures. * maintains a std::vector with static lifetime. */ template - class MethodContainer : public SetupMethod>, - public CallReflector> + class MethodContainer : public SetupMethod>, + public CallReflector> { - using MethodLambda = std::function < access::RStatus(std::any, _signature...) >; + using MethodLambda = std::function < Return (const rtl::RObject&, _signature...) >; public: - //every MethodContainer will have a unique-id. - static const std::size_t& getContainerId() { - return m_containerId; + //every MethodContainer will have a unique-id. + FORCE_INLINE static std::size_t getContainerId() { + //holds unique-id + static const std::size_t containerId = generate_unique_id(); + return containerId; } //get the vector holding lambdas as 'const-ref' - static const std::vector& getMethodFunctors() { - return m_methodPtrs; + FORCE_INLINE static const std::vector& getMethodFunctors() { + static std::vector& functorTable = getFunctorTable(); + return functorTable; } //get container type as string template - static const std::string getSignatureStr() { - return (TypeId<_returnType>::toString() + " " + TypeId<_recordType>::toString() + "::(" + TypeId<_signature...>::toString() + ") const"); + static std::string getSignatureStr() + { + return (TypeId<_returnType>::toString() + " " + TypeId<_recordType>::toString() + + "::(" + TypeId<_signature...>::toString() + ") const"); } private: - //holds unique-id - static const std::size_t m_containerId; - //vector holding lambdas - static std::vector m_methodPtrs; + static std::vector& getFunctorTable() { + static std::vector functorTable; + return functorTable; + } /* @method: pushBack @params: pFunctor (lambda containing const-member-function functor call) pGetIndex (lambda providing index if the functor is already registered) pUpdate (lambda updating the already registered functors set) @return: index of newly added or already existing lambda in vector 'm_methodPtrs'. - */ static const std::size_t pushBack(const MethodLambda& pFunctor, - std::function pGetIndex, - std::function pUpdateIndex) + */ static std::size_t pushBack(const MethodLambda& pFunctor, + std::function pGetIndex, + std::function pUpdateIndex) { //critical section, thread safe. static std::mutex mtx; std::lock_guard lock(mtx); std::size_t index = pGetIndex(); - if (index == -1) { - index = m_methodPtrs.size(); + if (index == rtl::index_none) { + index = getFunctorTable().size(); pUpdateIndex(index); - m_methodPtrs.push_back(pFunctor); + getFunctorTable().push_back(pFunctor); } return index; } //friends :) friend ReflectionBuilder; - friend SetupMethod>; + friend SetupMethod>; }; - - template - const std::size_t MethodContainer::m_containerId = g_containerIdCounter.fetch_add(1); - - template - std::vector::MethodLambda> - MethodContainer::m_methodPtrs; } } \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/MethodInvoker.h b/ReflectionTemplateLib/detail/inc/MethodInvoker.h new file mode 100644 index 00000000..198cf761 --- /dev/null +++ b/ReflectionTemplateLib/detail/inc/MethodInvoker.h @@ -0,0 +1,80 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +namespace rtl { + + //forward decls + class Method; +} + +namespace rtl::detail { + + template + struct DefaultInvoker + { + //the method to be called. + const Method* m_method; + + //the object on which, the method needs to be called. + const RObject* m_target; + + template + struct Invoker { + + template + static Return invoke(const Method& pMethod, const RObject& pTarget, _args&&...); + }; + + public: + + template + Return call(_args&&...) const noexcept; + + template + constexpr Return operator()(_args&&...params) const noexcept { + return call(std::forward<_args>(params)...); + } + + friend Method; + }; + + + template + struct NonConstInvoker + { + //the method to be called. + const Method* m_method; + + //the object on which, the method needs to be called. + const RObject* m_target; + + template + struct Invoker { + + template + static Return invoke(const Method& pMethod, const RObject& pTarget, _args&&...); + }; + + public: + + template + Return call(_args&&...) const noexcept; + + template + constexpr Return operator()(_args&&...params) const noexcept { + return call(std::forward<_args>(params)...); + } + + friend Method; + }; +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/MethodInvoker.hpp b/ReflectionTemplateLib/detail/inc/MethodInvoker.hpp new file mode 100644 index 00000000..fb06f8cd --- /dev/null +++ b/ReflectionTemplateLib/detail/inc/MethodInvoker.hpp @@ -0,0 +1,148 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include "Method.h" +#include "RObject.h" +#include "MethodInvoker.h" +#include "MethodContainer.h" + +namespace rtl::detail +{ +/* @method: call() + @params: params... (corresponding to functor associated with 'm_method') + @return: RObject, indicating success of the reflected call. + * invokes non-static-member-function functor associated with 'm_method' on object 'm_target'. +*/ template + template + FORCE_INLINE Return DefaultInvoker<_signature...>::call(_args&& ...params) const noexcept + { + //Only static-member-functions have Qualifier- 'methodQ::None' + if (m_method->getQualifier() == methodQ::None) [[unlikely]] { + return static_cast(*m_method).bind().call(std::forward<_args>(params)...); + } + else if (m_target->isEmpty()) [[unlikely]] { + //if the target is empty. + return { error::EmptyRObject, RObject{} }; + } + else if (m_target->getTypeId() != m_method->getRecordTypeId()) [[unlikely]] { + //if the m_target's type-id & type-id of the 'class/struct' owner of the associated functor(m_method's) do not match. + return { error::TargetMismatch, RObject{} }; + } + else [[likely]] + { + if constexpr (sizeof...(_signature) == 0) { + // executes when bind doesn't have any explicit signature types specified. (e.g. perfect-forwaring) + return Invoker...>::invoke(*m_method, *m_target, std::forward<_args>(params)...); + } + else { + return Invoker<_signature...>::invoke(*m_method, *m_target, std::forward<_args>(params)...); + } + } + } + + + // Invoker struct's static method definition + template + template + template + FORCE_INLINE Return + DefaultInvoker<_signature...>::Invoker<_invokSignature...>::invoke(const Method& pMethod, + const RObject& pTarget, + _args&&... params) + { + using containerConst = detail::MethodContainer; + std::size_t constMethodIndex = pMethod.hasSignatureId(containerConst::getContainerId()); + + if (constMethodIndex != rtl::index_none) [[likely]] + { + return containerConst::template forwardCall<_args...>(pTarget, constMethodIndex, std::forward<_args>(params)...); + } + else [[unlikely]] + { + using containerNonConst = detail::MethodContainer; + std::size_t nonConstMethodIndex = pMethod.hasSignatureId(containerNonConst::getContainerId()); + + if (nonConstMethodIndex != rtl::index_none) + { + if (!pTarget.isConstCastSafe()) { + return { error::ConstOverloadMissing, RObject{} }; + } + return containerNonConst::template forwardCall<_args...>(pTarget, nonConstMethodIndex, std::forward<_args>(params)...); + } + } + return { error::SignatureMismatch, RObject{} }; + } +} + + +namespace rtl::detail +{ +/* @method: call() + @params: params... (corresponding to functor associated with 'm_method') + @return: RObject, indicating success of the reflected call. + * invokes non-static-member-function functor associated with 'm_method' on object 'm_target'. +*/ template + template + FORCE_INLINE Return NonConstInvoker<_signature...>::call(_args&& ...params) const noexcept + { + if (m_method->getQualifier() == methodQ::None) [[unlikely]] { + return static_cast(*m_method).bind().call(std::forward<_args>(params)...); + } + else if (m_target->isEmpty()) [[unlikely]] { + //if the target is empty. + return { error::EmptyRObject, RObject{} }; + } + else if (m_target->getTypeId() != m_method->getRecordTypeId()) [[unlikely]] { + //if the m_target's type-id & type-id of the 'class/struct' owner of the associated functor(m_method's) do not match. + return { error::TargetMismatch, RObject{} }; + } + else [[likely]] + { + if constexpr (sizeof...(_signature) == 0) { + return Invoker...>::invoke(*m_method, *m_target, std::forward<_args>(params)...); + } + else { + return Invoker<_signature...>::invoke(*m_method, *m_target, std::forward<_args>(params)...); + } + } + } + + + // Invoker struct's static method definition + template + template + template + FORCE_INLINE Return + NonConstInvoker<_signature...>::Invoker<_invokSignature...>::invoke(const Method& pMethod, + const RObject& pTarget, + _args&&... params) + { + using container0 = detail::MethodContainer; + const std::size_t index = pMethod.hasSignatureId(container0::getContainerId()); + if (index != rtl::index_none) [[likely]] { + return container0::template forwardCall<_args...>(pTarget, index, std::forward<_args>(params)...); + } + else + { + // check if the const-overload method is present. + using container2 = detail::MethodContainer; + std::size_t index = pMethod.hasSignatureId(container2::getContainerId()); + if (index != rtl::index_none) { + // So, const-overload is present and non-const overload is not registered or doesn't exists. + return { error::NonConstOverloadMissing, RObject{} }; + } + // else the signature might be wrong. + return { error::SignatureMismatch , RObject{} }; + } + } +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/RObjExtracter.h b/ReflectionTemplateLib/detail/inc/RObjExtracter.h new file mode 100644 index 00000000..8f1c11ef --- /dev/null +++ b/ReflectionTemplateLib/detail/inc/RObjExtracter.h @@ -0,0 +1,151 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include "RObject.h" + +namespace rtl::detail +{ + struct RObjExtractor + { + friend RObject; + + const RObject* m_rObj; + + template + FORCE_INLINE static const T* getPointer(const std::any& pObject, const EntityKind pEntityKind) noexcept + { + switch (pEntityKind) + { + case EntityKind::Ptr: { + return *(std::any_cast(&pObject)); + } + case EntityKind::Value: { + return std::any_cast(&pObject); + } + default: return nullptr; + } + return nullptr; + } + + + template + FORCE_INLINE const T* getPointer() const noexcept + { + switch (m_rObj->m_objectId.m_containsAs) + { + case EntityKind::Ptr: { + return *(std::any_cast(&(m_rObj->m_object.value()))); + } + case EntityKind::Wrapper: { + return getFromWrapper(); + } + case EntityKind::Value: { + return std::any_cast(&(m_rObj->m_object.value())); + } + default: return nullptr; + } + return nullptr; + } + + + template = 0> + FORCE_INLINE auto getWrapper() const noexcept -> const RObjectUPtr::value_type>* + { + if (m_rObj->m_objectId.m_wrapperType == detail::Wrapper::Unique) + { + using _T = traits::std_wrapper::value_type; + if constexpr (traits::is_const_v<_T>) + { + if (m_rObj->m_objectId.m_isWrappingConst) + { + using U = detail::RObjectUPtr; + return std::any_cast(&(m_rObj->m_object.value())); + } + } + else + { + using U = detail::RObjectUPtr<_T>; + return std::any_cast(&(m_rObj->m_object.value())); + } + } + return nullptr; + } + + + template = 0> + FORCE_INLINE const T* getWrapper() const noexcept + { + if (m_rObj->m_objectId.m_wrapperType == detail::Wrapper::Shared) + { + using _T = traits::std_wrapper::value_type; + if constexpr (traits::is_const_v<_T>) + { + if (m_rObj->m_objectId.m_isWrappingConst) { + using U = std::shared_ptr; + return std::any_cast(&(m_rObj->m_object.value())); + } + } + else + { + using U = std::shared_ptr<_T>; + return std::any_cast(&(m_rObj->m_object.value())); + } + } + return nullptr; + } + + + template + FORCE_INLINE const T* getFromWrapper() const noexcept + { + if constexpr (std::is_destructible_v) + { + if (m_rObj->m_objectId.m_wrapperType == detail::Wrapper::Unique) + { + if (m_rObj->m_objectId.m_isWrappingConst) { + using U = detail::RObjectUPtr; + const U* uptr = std::any_cast(&(m_rObj->m_object.value())); + if (uptr != nullptr) { + return uptr->get(); + } + } + else { + using U = detail::RObjectUPtr; + const U* uptr = std::any_cast(&(m_rObj->m_object.value())); + if (uptr != nullptr) { + return uptr->get(); + } + } + } + if (m_rObj->m_objectId.m_wrapperType == detail::Wrapper::Shared) + { + if (m_rObj->m_objectId.m_isWrappingConst) { + using U = std::shared_ptr; + const U* sptr = std::any_cast(&(m_rObj->m_object.value())); + if (sptr != nullptr) { + return sptr->get(); + } + } + else { + using U = std::shared_ptr; + const U* sptr = std::any_cast(&(m_rObj->m_object.value())); + if (sptr != nullptr) { + return sptr->get(); + } + } + } + } + return nullptr; + } + }; +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/RObjectBuilder.h b/ReflectionTemplateLib/detail/inc/RObjectBuilder.h new file mode 100644 index 00000000..b847dacc --- /dev/null +++ b/ReflectionTemplateLib/detail/inc/RObjectBuilder.h @@ -0,0 +1,76 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include "rtl_traits.h" + +namespace rtl { + class RObject; + struct Return; +} + +namespace rtl::detail +{ + template + struct RObjectBuilder + { + RObjectBuilder() = delete; + RObjectBuilder(const RObjectBuilder&) = delete; + + template requires (_allocOn == alloc::Heap) + static RObject build(T&& pVal, std::size_t pClonerIndex, bool pIsConstCastSafe) noexcept; + + template requires (_allocOn == alloc::Stack) + static RObject build(T&& pVal, std::size_t pClonerIndex, bool pIsConstCastSafe) noexcept; + }; +} + + +namespace rtl +{ + inline const std::size_t getRtlManagedHeapInstanceCount() + { + return RObject::getInstanceCounter(); + } + + + template + inline RObject reflect(T(&pArr)[N]) noexcept + { + if constexpr (std::is_same_v, char>) { + return detail::RObjectBuilder::template + build(std::string_view(pArr, N - 1), rtl::index_none, !traits::is_const_v); + } + else { + return detail::RObjectBuilder>::template + build(std::vector(pArr, pArr + N), rtl::index_none, !traits::is_const_v); + } + } + + + template + inline RObject reflect(T&& pVal) noexcept + { + using _T = traits::raw_t; + if constexpr (traits::std_wrapper<_T>::type == detail::Wrapper::None) + { + return detail::RObjectBuilder::template + build(std::forward(pVal), rtl::index_none, !traits::is_const_v); + } + else + { + constexpr bool isConstCastSafe = !traits::is_const_v::value_type>; + return detail::RObjectBuilder::template + build(std::forward(pVal), rtl::index_none, isConstCastSafe); + } + } +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/RObjectBuilder.hpp b/ReflectionTemplateLib/detail/inc/RObjectBuilder.hpp new file mode 100644 index 00000000..5de6e142 --- /dev/null +++ b/ReflectionTemplateLib/detail/inc/RObjectBuilder.hpp @@ -0,0 +1,84 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include + +#include "RObject.hpp" +#include "RObjectUPtr.h" +#include "RObjectBuilder.h" + +namespace rtl::detail +{ + template + FORCE_INLINE const std::vector& getConverters() noexcept + { + // extract wrapper info. + using _W = traits::std_wrapper>; + // extract Un-Qualified raw type. + using _T = traits::raw_t>; + return rtl::detail::ReflectCast<_T>::getConversions(); + } + + + template + template requires (_allocOn == alloc::Heap) + FORCE_INLINE RObject RObjectBuilder::build(T&& pVal, std::size_t pClonerIndex, bool pIsConstCastSafe) noexcept + { + using _T = traits::raw_t; + return RObject( std::any{ + std::in_place_type>, + RObjectUPtr<_T>(std::unique_ptr<_T>(static_cast<_T*>(pVal))) + }, + RObjectId::create, alloc::Heap>(pClonerIndex, pIsConstCastSafe), + &getConverters>()); + } + + + template + template requires (_allocOn == alloc::Stack) + FORCE_INLINE RObject RObjectBuilder::build(T&& pVal, std::size_t pClonerIndex, bool pIsConstCastSafe) noexcept + { + using _T = traits::raw_t; + constexpr bool isRawPointer = std::is_pointer_v>; + + if constexpr (isRawPointer) + { + return RObject( std::any { static_cast(pVal) }, + RObjectId::create(pClonerIndex, pIsConstCastSafe), + &getConverters() ); + } + else + { + if constexpr (traits::std_wrapper<_T>::type == Wrapper::Unique) + { + using U = traits::std_wrapper<_T>::value_type; + return RObject( std::any { + std::in_place_type>, + RObjectUPtr(std::move(pVal)) + }, + RObjectId::create(pClonerIndex, pIsConstCastSafe), + &getConverters() ); + } + else + { + static_assert(std::is_copy_constructible_v<_T>, "T must be copy-constructible (std::any requires this)."); + return RObject( std::any { + std::in_place_type, + std::forward(pVal) + }, + RObjectId::create(pClonerIndex, pIsConstCastSafe), + &getConverters() ); + } + } + } +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/RObjectId.h b/ReflectionTemplateLib/detail/inc/RObjectId.h new file mode 100644 index 00000000..5455ca9f --- /dev/null +++ b/ReflectionTemplateLib/detail/inc/RObjectId.h @@ -0,0 +1,75 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include +#include +#include "ReflectCast.h" + +namespace rtl { + class RObject; +} + +namespace rtl::detail +{ + struct RObjectId + { + bool m_isWrappingConst; + bool m_isConstCastSafe; + + std::size_t m_typeId; + std::size_t m_clonerIndex; + std::size_t m_wrapperTypeId; + + alloc m_allocatedOn; + Wrapper m_wrapperType; + EntityKind m_containsAs; + + GETTER(std::size_t, TypeId, m_typeId) + GETTER(EntityKind, ContainedAs, m_containsAs) + + template + FORCE_INLINE static constexpr EntityKind getEntityKind() noexcept + { + using W = traits::std_wrapper>; + using _T = traits::raw_t>; + constexpr bool isRawPtr = traits::is_raw_ptr_v; + constexpr bool isWrapper = (W::type != Wrapper::None); + + if constexpr (isWrapper && !isRawPtr) { + return EntityKind::Wrapper; + } + else if constexpr (isRawPtr && !isWrapper) { + return EntityKind::Ptr; + } + else if constexpr (!isWrapper && !isRawPtr) { + return EntityKind::Value; + } + } + + + template + FORCE_INLINE static RObjectId create(std::size_t pClonerIndex, bool pIsConstCastSafe) noexcept + { + // extract wrapper info. + using _W = traits::std_wrapper>; + // extract Un-Qualified raw type. + using _T = traits::raw_t>; + constexpr EntityKind entityKind = getEntityKind(); + + const std::size_t wrapperId = _W::id(); + const std::size_t typeId = rtl::detail::TypeId<_T>::get(); + constexpr bool isWrappingConst = (_W::type != Wrapper::None && traits::is_const_v); + return RObjectId{ isWrappingConst, pIsConstCastSafe, typeId, pClonerIndex, wrapperId, _allocOn, _W::type, entityKind }; + } + }; +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/RObjectUPtr.h b/ReflectionTemplateLib/detail/inc/RObjectUPtr.h new file mode 100644 index 00000000..67137208 --- /dev/null +++ b/ReflectionTemplateLib/detail/inc/RObjectUPtr.h @@ -0,0 +1,100 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once +#include +#include +#include +#include + +#include "RObject.h" + +/*------------------------------------------------------------------------------------------ + RObjectUPtr + + Purpose: + -------- + MSVC's std::any refuses to store std::unique_ptr directly because + std::unique_ptr is *not copy-constructible*. This restriction makes sense + for safety, but it prevents us from using std::any to hold unique_ptr-managed + objects in a reflection context. + + GCC/Clang technically allow it in certain cases by relying on moves, but + their behavior isn't guaranteed consistent across all standards. + We need *predictable cross-compiler behavior*. + + Solution: + --------- + RObjectUPtr is a thin, move-only wrapper that *pretends* to be copyable, + but its "copy constructor" does nothing and RTL makes sure its never called. + This satisfies std::any's requirement for a copy constructor, while ensuring + only one instance ever truly owns the resource. + + Key Properties: + --------------- + 1. Copy constructor - never gets called. + 2. Move constructor works as expected. + 3. Deleted copy/move assignment operators to prevent post-construction reassignment. + 4. Tracks heap allocation count via RObject's internal counter for lifetime diagnostics. + +--------------------------------------------------------------------------------------------*/ + +namespace rtl::detail +{ + template + struct RObjectUPtr + { + RObjectUPtr() = delete; + RObjectUPtr& operator=(const RObjectUPtr&) = delete; + RObjectUPtr& operator=(RObjectUPtr&& other) = delete; + + // Copy constructor: empty, just to trick the 'std::any'. NEVER GETS CALLED!! + RObjectUPtr(const RObjectUPtr& pOther) { + assert(false && "RObjectUPtr(const RObjectUPtr&) must never get called."); + } + + // Move constructor: transfers ownership as usual. + RObjectUPtr(RObjectUPtr&& pOther) noexcept + : m_uniquePtr(std::move(pOther.m_uniquePtr)) { + pOther.m_uniquePtr = nullptr; + } + + // Construct directly from std::unique_ptr, tracking RTL-owned heap allocations. + RObjectUPtr(std::unique_ptr&& pUniquePtr) + : m_uniquePtr(std::move(pUniquePtr)) { + RObject::getInstanceCounter().fetch_add(1, std::memory_order_relaxed); + } + + // Destructor: decrements allocation count if we still own the object. + ~RObjectUPtr() { + if (m_uniquePtr) { + RObject::getInstanceCounter().fetch_sub(1, std::memory_order_relaxed); + } + } + + const T* get() const { + return m_uniquePtr.get(); + } + + std::unique_ptr release() const + { + if (m_uniquePtr) { + RObject::getInstanceCounter().fetch_sub(1, std::memory_order_relaxed); + return std::move(m_uniquePtr); + } + return nullptr; + } + + private: + + mutable std::unique_ptr m_uniquePtr; + }; +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/ReflectCast.h b/ReflectionTemplateLib/detail/inc/ReflectCast.h new file mode 100644 index 00000000..ae748ba8 --- /dev/null +++ b/ReflectionTemplateLib/detail/inc/ReflectCast.h @@ -0,0 +1,51 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include +#include +#include + +#include "rtl_traits.h" + +namespace rtl { + + class CxxMirror; +} + +namespace rtl::detail +{ + class ReflectedConversions + { + static void init(); + + friend rtl::CxxMirror; + }; + + + template + class ReflectCast + { + static std::vector>& conversions() { + static std::vector> converters; + return converters; + } + + public: + + template static void pushConversion(); + + static const std::vector>& getConversions() { + return conversions(); + } + }; +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/ReflectCast.hpp b/ReflectionTemplateLib/detail/inc/ReflectCast.hpp new file mode 100644 index 00000000..85b405aa --- /dev/null +++ b/ReflectionTemplateLib/detail/inc/ReflectCast.hpp @@ -0,0 +1,60 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include "TypeId.h" +#include "ReflectCast.h" +#include "ConversionUtils.h" + +namespace rtl::detail +{ + template + template + inline void ReflectCast<_fromType>::pushConversion() + { +// if constexpr (traits::is_safe_conversion_v<_fromType, _toType>) + { + const auto& conversion = [](const std::any& pSrc, const EntityKind& pSrcEntityKind, EntityKind& pNewEntityKind) -> std::any + { + try + { + bool isPointer = (pSrcEntityKind == EntityKind::Ptr); + const _fromType& srcRef = (isPointer ? *(std::any_cast(pSrc)) : std::any_cast(pSrc)); + + if constexpr (std::is_convertible_v<_fromType*, _toType*>) + { + pNewEntityKind = pSrcEntityKind; + return std::any(std::in_place_type, static_cast(srcRef)); + } + else if constexpr ((std::is_convertible_v<_fromType, _toType> && + !std::is_convertible_v<_fromType&, const _toType&>) || + std::is_constructible_v<_toType, const _fromType&>) { + + pNewEntityKind = EntityKind::Value; + return std::any(std::in_place_type<_toType>, _toType(srcRef)); + } + else { + + pNewEntityKind = EntityKind::None; + return std::any(); + } + } + catch (const std::bad_any_cast&) + { + pNewEntityKind = EntityKind::None; + return std::any(); + } + }; + conversions().emplace_back(std::pair(TypeId<_toType>::get(), conversion)); + } + } +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/ReflectCastUtil.h b/ReflectionTemplateLib/detail/inc/ReflectCastUtil.h new file mode 100644 index 00000000..782300e2 --- /dev/null +++ b/ReflectionTemplateLib/detail/inc/ReflectCastUtil.h @@ -0,0 +1,80 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once + +#include "ReflectCast.h" + +namespace +{ + template + struct TypeConversion + { + using from = From; + using to = To; + }; + + + template + auto make_pairs_for_index(std::index_sequence) + { + using From = std::tuple_element_t; + return std::tuple>...>{}; + } + + + template + auto make_all_pairs(std::index_sequence) + { + return std::tuple_cat( + make_pairs_for_index + ( + std::make_index_sequence::value>{} + )... + ); + } + + + template + constexpr auto make_conversion_pairs() + { + return make_all_pairs( + std::make_index_sequence::value>{} + ); + } + + + template + void register_all_conversions_impl(std::index_sequence) + { + (..., + ( + []{ + using Conversion = std::tuple_element_t; + using From = typename Conversion::from; + using To = typename Conversion::to; + if constexpr (!std::is_same_v) { + rtl::detail::ReflectCast::template pushConversion(); + } + }() + ) + ); + } + + + template + void register_all_conversions() + { + register_all_conversions_impl( + std::make_index_sequence::value> { } + ); + } +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/ReflectionBuilder.h b/ReflectionTemplateLib/detail/inc/ReflectionBuilder.h index 8977b71a..abc75d98 100644 --- a/ReflectionTemplateLib/detail/inc/ReflectionBuilder.h +++ b/ReflectionTemplateLib/detail/inc/ReflectionBuilder.h @@ -1,3 +1,14 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include "Function.h" @@ -9,41 +20,35 @@ namespace rtl { /* @class: ReflectionBuilder * adds the given non-member, static-member 'functor' to the 'FunctionContainer'. * adds the given const/non-const member, non-static-member 'functor' to the 'MethodContainer'. - * adds the constructor and destructor to 'FunctionContainer'. + * adds the constructor to 'FunctionContainer'. */ class ReflectionBuilder { protected: - const std::string& m_record; - const std::string& m_function; - const std::string& m_namespace; + const std::size_t m_recordId; + const std::string_view m_record; + const std::string_view m_function; + const std::string_view m_namespace; - explicit ReflectionBuilder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction); + ReflectionBuilder(const std::string_view pFunction, std::size_t pRecordId, + const std::string_view pNamespace = "", + const std::string_view pRecord = ""); //adds constructor (any overload) to the 'FunctorContainer'. template - const access::Function buildConstructor() const; - - //adds copy constructor to the 'FunctorContainer'. - template - const access::Function buildCopyConstructor() const; - - //adds const-copy constructor to the 'FunctorContainer'. - template - const access::Function buildConstCopyConstructor() const; + const Function buildConstructor() const; //adds 'pFunctor' to the 'FunctorContainer'. template - const access::Function buildFunctor(_returnType(*pFunctor)(_signature...)) const; + const Function buildFunctor(_returnType(*pFunctor)(_signature...)) const; //adds 'pFunctor' to the 'MethodContainer'. template - const access::Function buildMethodFunctor(_returnType(_recordType::* pFunctor)(_signature...)) const; + const Function buildMethodFunctor(_returnType(_recordType::* pFunctor)(_signature...)) const; //adds 'pFunctor' to the 'MethodContainer'. template - const access::Function buildMethodFunctor(_returnType(_recordType::* pFunctor)(_signature...) const) const; + const Function buildMethodFunctor(_returnType(_recordType::* pFunctor)(_signature...) const) const; }; } } \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/ReflectionBuilder.hpp b/ReflectionTemplateLib/detail/inc/ReflectionBuilder.hpp index cf3c968e..29e96bb4 100644 --- a/ReflectionTemplateLib/detail/inc/ReflectionBuilder.hpp +++ b/ReflectionTemplateLib/detail/inc/ReflectionBuilder.hpp @@ -1,119 +1,98 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include "ReflectionBuilder.h" #include "FunctorContainer.h" #include "MethodContainer.h" +#include "SetupMethod.hpp" +#include "SetupFunction.hpp" +#include "SetupConstructor.hpp" + +namespace rtl::detail +{ -namespace rtl { - - namespace detail - { - inline ReflectionBuilder::ReflectionBuilder(const std::string& pNamespace, const std::string& pRecord, - const std::string& pFunction) - : m_record(pRecord) - , m_function(pFunction) - , m_namespace(pNamespace) { - } + inline ReflectionBuilder::ReflectionBuilder(const std::string_view pFunction, std::size_t pRecordId, + const std::string_view pNamespace /* = ""*/, + const std::string_view pRecord /* = ""*/) + : m_recordId(pRecordId) + , m_record(pRecord) + , m_function(pFunction) + , m_namespace(pNamespace) + { } - /* @method: buildFunctor() - @return: 'Function', object associated with the given functor. - @param: 'pFunctor', function pointer with, - * '_returnType' & '_signature...'(auto deduced). - * adds the function pointer in 'FunctorContainer' - * accepts only a non-member or static-member function pointer. - * builds the 'Function' object containing hash-key & meta-data for the given functor. - */ template - inline const access::Function ReflectionBuilder::buildFunctor(_returnType(*pFunctor)(_signature...)) const - { - const std::string& typeStr = detail::TypeId<_signature...>::toString(); - const detail::FunctorId functorId = detail::FunctorContainer<_signature...>::addFunctor(pFunctor); - return access::Function(m_namespace, m_record, m_function, functorId, TypeId<>::None, TypeQ::None); - } +/* @method: buildFunctor() + @return: 'Function', object associated with the given functor. + @param: 'pFunctor', function pointer with, + * '_returnType' & '_signature...'(auto deduced). + * adds the function pointer in 'FunctorContainer' + * accepts only a non-member or static-member function pointer. + * builds the 'Function' object containing hash-key & meta-data for the given functor. +*/ template + inline const Function ReflectionBuilder::buildFunctor(_returnType(*pFunctor)(_signature...)) const + { + using Container = FunctorContainer< traits::remove_const_if_not_reference<_signature>...>; + const FunctorId& functorId = Container::template addFunctor<_returnType, _signature...>(pFunctor, m_recordId); + return Function(m_namespace, m_record, m_function, functorId, m_recordId, methodQ::None); + } - /* @method: buildFunctor() - @return: 'Function', object associated with the given functor. - @param: 'pFunctor', function pointer with, '_recordType' (class/struct), - * '_returnType' & '_signature...'(auto deduced). - * adds the function pointer in 'MethodContainer' - * accepts only a non-static, non-const member function pointer. - * builds the 'Function' object containing hash-key & meta-data for the given functor. - */ template - inline const access::Function ReflectionBuilder::buildMethodFunctor(_returnType(_recordType::* pFunctor)(_signature...)) const - { - const std::string& typeStr = detail::TypeId<_signature...>::toString(); - const detail::FunctorId functorId = detail::MethodContainer::addFunctor(pFunctor); - return access::Function(m_namespace, m_record, m_function, functorId, TypeId<_recordType>::get(), TypeQ::Mute); - } +/* @method: buildFunctor() + @return: 'Function', object associated with the given functor. + @param: 'pFunctor', function pointer with, '_recordType' (class/struct), + * '_returnType' & '_signature...'(auto deduced). + * adds the function pointer in 'MethodContainer' + * accepts only a non-static, non-const member function pointer. + * builds the 'Function' object containing hash-key & meta-data for the given functor. +*/ template + inline const Function ReflectionBuilder::buildMethodFunctor(_returnType(_recordType::* pFunctor)(_signature...)) const + { + using Container = MethodContainer...>; + const FunctorId& functorId = Container::template addFunctor<_recordType, _returnType, _signature...>(pFunctor); + return Function(m_namespace, m_record, m_function, functorId, m_recordId, methodQ::NonConst); + } - /* @method: buildMethodFunctor() - @return: 'Function', object associated with the given functor. - @param: 'pFunctor', function pointer with, '_recordType' (class/struct), - * '_returnType' & '_signature...'(auto deduced). - * adds the function pointer in 'MethodContainer' - * accepts only a const member function pointer. - * builds the 'Function' object containing hash-key & meta-data for the given functor. - */ template - inline const access::Function ReflectionBuilder::buildMethodFunctor(_returnType(_recordType::* pFunctor)(_signature...) const) const - { - const std::string& typeStr = detail::TypeId<_signature...>::toString(); - const detail::FunctorId functorId = detail::MethodContainer::addFunctor(pFunctor); - return access::Function(m_namespace, m_record, m_function, functorId, TypeId<_recordType>::get(), TypeQ::Const); - } +/* @method: buildMethodFunctor() + @return: 'Function', object associated with the given functor. + @param: 'pFunctor', function pointer with, '_recordType' (class/struct), + * '_returnType' & '_signature...'(auto deduced). + * adds the function pointer in 'MethodContainer' + * accepts only a const member function pointer. + * builds the 'Function' object containing hash-key & meta-data for the given functor. +*/ template + inline const Function ReflectionBuilder::buildMethodFunctor(_returnType(_recordType::* pFunctor)(_signature...) const) const + { + using Container = MethodContainer...>; + const FunctorId& functorId = Container::template addFunctor<_recordType, _returnType, _signature...>(pFunctor); + return Function(m_namespace, m_record, m_function, functorId, m_recordId, methodQ::Const); + } - /* @method: buildConstructor() - @return: 'Function', object associated with the (specified parametrized) constructor. - @param: '_recordType'(class/struct type) & '_ctorSignature...' (explicitly specified), - * adds the lambda invoking constructor (type-erased) in 'FunctorContainer' - * builds the 'Function' object containing hash-key & meta-data for the constructor. - * also adds the lambda for invoking the destructor and returns its hash-key with the constructor's 'Function'. - */ template - inline const access::Function ReflectionBuilder::buildConstructor() const - { - const detail::FunctorId functorId = detail::FunctorContainer<_ctorSignature...>::template addConstructor<_recordType, _ctorSignature...>(); - const std::string& typeStr = detail::TypeId<_ctorSignature...>::toString(); - const access::Function constructor = access::Function(m_namespace, m_record, m_function, functorId, TypeId<_recordType>::get(), TypeQ::None); - //add the destructor's 'FunctorId' to the constructor's functorIds list. - constructor.getFunctorIds().emplace_back(detail::FunctorContainer::addDestructor<_recordType>()); - return constructor; - } +/* @method: buildConstructor() + @return: 'Function', object associated with the (specified parametrized) constructor. + @param: '_recordType'(class/struct type) & '_ctorSignature...' (explicitly specified), + * adds the lambda invoking constructor (type-erased) in 'FunctorContainer' + * builds the 'Function' object containing hash-key & meta-data for the constructor. +*/ template + inline const Function ReflectionBuilder::buildConstructor() const + { + using Container = FunctorContainer < rtl::alloc, std::size_t, traits::remove_const_if_not_reference<_ctorSignature>... > ; + const FunctorId& functorId = Container::template addConstructor<_recordType, _ctorSignature...>(); + const FunctorId& copyCtorId = traits::Cloner::template addCopyConstructor<_recordType, const RObject&>(); + const Function& ctorFunction = Function(m_namespace, m_record, m_function, functorId, m_recordId, methodQ::None); - /* @method: buildCopyConstructor() - @return: 'Function', object associated with the copy constructor. - @param: '_recordType'(class/struct type) & '_ctorSignature...' ('_recordType&', explicitlly specified internally), - * adds the lambda invoking copy constructor (type-erased) in 'FunctorContainer' - * builds the 'Function' object containing hash-key & meta-data for the copy constructor. - * also adds the lambda for invoking the destructor and returns its hash-key with the constructor's 'Function'. - */ template - inline const access::Function ReflectionBuilder::buildCopyConstructor() const - { - const detail::FunctorId functorId = detail::FunctorContainer::addCopyConstructor<_recordType>(); - const std::string& typeStr = detail::TypeId<_ctorSignature...>::toString(); - const access::Function constructor = access::Function(m_namespace, m_record, m_function, functorId, TypeId<_recordType>::get(), TypeQ::None); - //add the destructor's 'FunctorId' to the constructor's functorIds list. - constructor.getFunctorIds().emplace_back(detail::FunctorContainer::addDestructor<_recordType>()); - return constructor; - } - - - /* @method: buildConstCopyConstructor() - @return: 'Function', object associated with the copy constructor. - @param: '_recordType'(class/struct type) & '_ctorSignature...' ('const _recordType&', explicitlly specified internally), - * adds the lambda invoking copy constructor (type-erased) taking const-ref in 'FunctorContainer' - * builds the 'Function' object containing hash-key & meta-data for the const-copy constructor. - * also adds the lambda for invoking the destructor and returns its hash-key with the constructor's 'Function'. - */ template - inline const access::Function ReflectionBuilder::buildConstCopyConstructor() const - { - const detail::FunctorId functorId = detail::FunctorContainer::addConstCopyConstructor<_recordType>(); - const std::string& typeStr = detail::TypeId<_ctorSignature...>::toString(); - const access::Function constructor = access::Function(m_namespace, m_record, m_function, functorId, TypeId<_recordType>::get(), TypeQ::None); - //add the destructor's 'FunctorId' to the constructor's functorIds list. - constructor.getFunctorIds().emplace_back(detail::FunctorContainer::addDestructor<_recordType>()); - return constructor; - } + ctorFunction.getFunctorIds().push_back(copyCtorId); + return ctorFunction; } -} \ No newline at end of file +} diff --git a/ReflectionTemplateLib/detail/inc/SetupConstructor.h b/ReflectionTemplateLib/detail/inc/SetupConstructor.h index bcd991cc..44d06345 100644 --- a/ReflectionTemplateLib/detail/inc/SetupConstructor.h +++ b/ReflectionTemplateLib/detail/inc/SetupConstructor.h @@ -1,9 +1,22 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include "FunctorId.h" namespace rtl { + class RObject; + namespace detail { /* @struct: SetupConstructor @@ -14,23 +27,23 @@ namespace rtl { */ template class SetupConstructor { - protected: + template + using CtorLambda = std::function < Return(alloc, std::size_t, _signature...) >; - //adds the lambda wrapping destructor call to '_derivedType' (FunctorContainer) - template - static const detail::FunctorId addDestructor(); + template + static CtorLambda<_signature...> getConstructorCaller(); + + template + static CtorLambda<_signature...> getCopyConstructorCaller(); + + protected: //adds the lambda, wrapping constructor call, recordType(_signature...), to '_derivedType' (FunctorContainer) template static const detail::FunctorId addConstructor(); - //adds the lambda, wrapping constructor call, _recordType(_recordType&') to '_derivedType' (FunctorContainer) - template + template static const detail::FunctorId addCopyConstructor(); - - //adds the lambda, wrapping constructor call, _recordType(const _recordType&'), to '_derivedType' (FunctorContainer) - template - static const detail::FunctorId addConstCopyConstructor(); }; } } \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/SetupConstructor.hpp b/ReflectionTemplateLib/detail/inc/SetupConstructor.hpp index b63d0833..e2d025be 100644 --- a/ReflectionTemplateLib/detail/inc/SetupConstructor.hpp +++ b/ReflectionTemplateLib/detail/inc/SetupConstructor.hpp @@ -1,174 +1,169 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include -#include "RStatus.h" +#include "RObjectBuilder.hpp" #include "SetupConstructor.h" -namespace rtl +namespace rtl::detail { - namespace detail + template + template + inline SetupConstructor<_derivedType>::CtorLambda<_signature...> + SetupConstructor<_derivedType>::getConstructorCaller() { - /* @method: addDestructor() - @param: '_derivedType' (FunctorContainer), '_recordType' (class/struct type) - @return: 'FunctorId' object, a hash-key to lookup the destructor (wrapped in lambda) in the _derivedType's lambda-table. - * adds lambda (destructor-call-wrapped) in '_derivedType' (FunctorContainer). - * maintains a static var for already registered destructor for a particular class/struct type. - * thread safe, this method is uniquely generated for each '_recordType' (class/struct type). - */ template - template - inline const detail::FunctorId SetupConstructor<_derivedType>::addDestructor() + return [](alloc pAllocType, std::size_t pClonerIndex, _signature&&...params)-> Return { - //no destructor is registered yet for type '_recordType' if 'dctorIndex' is -1. - static std::size_t dctorIndex = -1; - - //will be called from '_derivedType' if the destructor not already registered. - const auto& updateIndex = [&](const std::size_t& pIndex) { - dctorIndex = pIndex; - }; - - //will be called from '_derivedType' to check if the destructor already registered. - const auto& getIndex = [&]()->const std::size_t { - return dctorIndex; - }; - - //destructor lambda. - const auto& functor = [](const std::any& pTarget)->access::RStatus + if constexpr (sizeof...(_signature) == 0 && !std::is_default_constructible_v<_recordType>) + { //default constructor, private or deleted. + return { error::TypeNotDefaultConstructible, RObject{} }; + } + else { - //cast will definitely succeed, will not throw since the object type is already validated. - _recordType* object = std::any_cast<_recordType*>(pTarget); - delete object; - return access::RStatus(Error::None); - }; + if (pAllocType == alloc::Stack) { + + if constexpr (!std::is_copy_constructible_v<_recordType>) { + return { + error::TypeNotCopyConstructible, RObject{} + }; + } + else { + return { + error::None, + RObjectBuilder<_recordType>::template + build(_recordType(std::forward<_signature>(params)...), pClonerIndex, true) + }; + } + } + else if (pAllocType == alloc::Heap) { + return { + error::None, + RObjectBuilder<_recordType*>::template + build(new _recordType(std::forward<_signature>(params)...), pClonerIndex, true) + }; + } + } + return { error::EmptyRObject, RObject{} }; //dead code. compiler warning omitted. + }; + } - //add the lambda in 'FunctorContainer'. - const std::size_t& index = _derivedType::pushBack(functor, getIndex, updateIndex); - return detail::FunctorId(index, TypeId<>::None, TypeId<_recordType>::get(), _derivedType::getContainerId(), - (std::string("~") + _derivedType::template getSignatureStr<_recordType>(true))); - } - /* @method: addConstructor() - @param: '_derivedType' (FunctorContainer), '_recordType' (class/struct), '_signature...' (ctor's args, explicitly specified) - @return: 'FunctorId' object, a hash-key to lookup the lambda in the _derivedType's lambda-table. - * adds lambda (wrapping constructor call) in '_derivedType' (FunctorContainer). - * maintains a static map to check for already registered constructor for a particular class/struct type. - * thread safe, this method is uniquely generated for each '_recordType' (class/struct type). - * adds constructor with any combination of arguments except, copy & const-ref copy constructors. - */ template - template - inline const detail::FunctorId SetupConstructor<_derivedType>::addConstructor() + template + template + inline SetupConstructor<_derivedType>::CtorLambda<_signature...> + SetupConstructor<_derivedType>::getCopyConstructorCaller() + { + if constexpr (std::is_copy_constructible_v<_recordType>) { - const auto& recordId = TypeId<_recordType>::get(); - const auto& containerId = _derivedType::getContainerId(); - const auto& hashKey = std::stoull(std::to_string(containerId) + std::to_string(recordId)); - - //maintaining a set of already registered constructors. - static std::map ctorSet; - - //will be called from '_derivedType' if the constructor not already registered. - const auto& updateIndex = [&](const std::size_t& pIndex) { - ctorSet.insert(std::make_pair(hashKey, pIndex)); - }; - - //will be called from '_derivedType' to check if the constructor already registered. - const auto& getIndex = [&]()->const std::size_t { - const auto& itr = ctorSet.find(hashKey); - return (itr != ctorSet.end() ? itr->second : -1); - }; - - //lambda containing constructor call. - const auto& functor = [=](_signature...params)->access::RStatus + return [](alloc pAllocOn, std::size_t pClonerIndex, const RObject& pOther) -> Return { - _recordType* retObj = new _recordType(params...); - return access::RStatus(std::make_any<_recordType*>(retObj), recordId, TypeQ::Mute); + const auto& srcObj = pOther.view<_recordType>()->get(); + switch (pAllocOn) + { + case alloc::Stack: + return { + error::None, + RObjectBuilder<_recordType>::template build(_recordType(srcObj), pClonerIndex, true) + }; + case alloc::Heap: + return { + error::None, + RObjectBuilder<_recordType*>::template build(new _recordType(srcObj), pClonerIndex, true) + }; + default: + return { + error::EmptyRObject, + RObject{} + }; + } }; - - //add the lambda in 'FunctorContainer'. - const std::size_t& index = _derivedType::pushBack(functor, getIndex, updateIndex); - return detail::FunctorId(index, TypeId<_recordType>::get(), recordId, containerId, - _derivedType::template getSignatureStr<_recordType>(true)); } - - - /* @method: addCopyConstructor() - @param: '_derivedType' (FunctorContainer), '_recordType' (class/struct). - @return: 'FunctorId' object, a hash-key to lookup the lambda in the _derivedType's lambda-table. - * adds lambda (wrapping copy-constructor call) in '_derivedType' (FunctorContainer). - * maintains a static map to check for already registered constructor for a particular class/struct type. - * thread safe, this method is uniquely generated for each '_recordType' (class/struct type). - * adds copy constructor with argument '_recordType&'. - */ template - template - inline const detail::FunctorId SetupConstructor<_derivedType>::addCopyConstructor() + else { - //no copy-constructor is registered yet for type '_recordType' if 'copyCtorIndex' is -1. - static std::size_t copyCtorIndex = -1; - - //will be called from '_derivedType' if the copy-constructor not already registered. - const auto& updateIndex = [&](const std::size_t& pIndex) { - copyCtorIndex = pIndex; - }; - - //will be called from '_derivedType' to check if the constructor already registered. - const auto& getIndex = [&]()->const std::size_t { - return copyCtorIndex; - }; - - const auto& recordId = TypeId<_recordType>::get(); - //lambda containing constructor call. - const auto& functor = [=](const std::any& pOther)->access::RStatus + return [](alloc pAllocOn, std::size_t pClonerIndex, const RObject&) -> Return { - //cast will definitely succeed, will not throw since the object type is already validated. - _recordType* srcObj = std::any_cast<_recordType*>(pOther); - _recordType* retObj = new _recordType(*srcObj); - return access::RStatus(std::make_any<_recordType*>(retObj), recordId, TypeQ::Mute); + return { + error::TypeNotCopyConstructible, + RObject{} + }; }; - - //add the lambda in 'FunctorContainer'. - const std::size_t& index = _derivedType::pushBack(functor, getIndex, updateIndex); - return detail::FunctorId(index, TypeId<_recordType>::get(), recordId, _derivedType::getContainerId(), - _derivedType::template getSignatureStr<_recordType>(true)); } + } - /* @method: addConstCopyConstructor() - @param: '_derivedType' (FunctorContainer), '_recordType' (class/struct). - @return: 'FunctorId' object, a hash-key to lookup the lambda in the _derivedType's lambda-table. - * adds lambda (wrapping copy-constructor with const-ref call) in '_derivedType' (FunctorContainer). - * maintains a static map to check for already registered constructor for a particular class/struct type. - * thread safe, this method is uniquely generated for each '_recordType' (class/struct type). - * adds copy constructor with argument 'const _recordType&'. - */ template - template - inline const detail::FunctorId SetupConstructor<_derivedType>::addConstCopyConstructor() - { - //no copy constructor with const-ref is registered yet for type '_recordType' if 'constCopyCtorIndex' is -1. - static std::size_t constCopyCtorIndex = -1; - - //will be called from '_derivedType' if the const-ref-copy-constructor not already registered. - const auto& updateIndex = [&](const std::size_t& pIndex) { - constCopyCtorIndex = pIndex; - }; - //will be called from '_derivedType' to check if the const-ref-copy-constructor already registered. - const auto& getIndex = [&]()->const std::size_t { - return constCopyCtorIndex; - }; +/* @method: addConstructor() + @param: '_derivedType' (FunctorContainer), '_recordType' (class/struct), '_signature...' (ctor's args, explicitly specified) + @return: 'FunctorId' object, a hash-key to lookup the lambda in the _derivedType's lambda-table. + * adds lambda (wrapping constructor call) in '_derivedType' (FunctorContainer). + * maintains a static map to check for already registered constructor for a particular class/struct type. + * thread safe, this method is uniquely generated for each '_recordType' (class/struct type). + * adds constructor with any combination of arguments except, copy & const-ref copy constructors. +*/ template + template + inline const detail::FunctorId SetupConstructor<_derivedType>::addConstructor() + { + std::size_t recordId = TypeId<_recordType>::get(); + std::size_t containerId = _derivedType::getContainerId(); + std::size_t hashKey = std::stoull(std::to_string(containerId) + std::to_string(recordId)); + + //maintaining a set of already registered constructors. + static std::map ctorSet; + + //will be called from '_derivedType' if the constructor not already registered. + const auto& updateIndex = [&](std::size_t pIndex)->void { + ctorSet.insert(std::make_pair(hashKey, pIndex)); + }; + + //will be called from '_derivedType' to check if the constructor already registered. + const auto& getIndex = [&]()-> std::size_t { + const auto& itr = ctorSet.find(hashKey); + return (itr != ctorSet.end() ? itr->second : index_none); + }; + + //add the lambda in 'FunctorContainer'. + std::size_t index = _derivedType::pushBack(getConstructorCaller<_recordType, _signature...>(), getIndex, updateIndex); + const auto& signatureStr = _derivedType::template getSignatureStr<_recordType>(true); + return detail::FunctorId(index, recordId, recordId, containerId, signatureStr); + } - const auto& recordId = TypeId<_recordType>::get(); - //lambda containing constructor call. - const auto& functor = [=](const std::any& pOther)->access::RStatus - { - //cast will definitely succeed, will not throw since the object type is already validated. - const _recordType* srcObj = std::any_cast<_recordType*>(pOther); - _recordType* retObj = new _recordType(*srcObj); - return access::RStatus(std::make_any<_recordType*>(retObj), recordId, TypeQ::Mute); - }; - //add the lambda in 'FunctorContainer'. - const std::size_t& index = _derivedType::pushBack(functor, getIndex, updateIndex); - return detail::FunctorId(index, TypeId<_recordType>::get(), recordId, _derivedType::getContainerId(), - _derivedType::template getSignatureStr<_recordType>(true)); - } + template + template + inline const detail::FunctorId SetupConstructor<_derivedType>::addCopyConstructor() + { + std::size_t recordId = TypeId<_recordType>::get(); + std::size_t containerId = _derivedType::getContainerId(); + std::size_t hashKey = std::stoull(std::to_string(containerId) + std::to_string(recordId)); + + //maintaining a set of already registered constructors. + static std::map ctorSet; + + //will be called from '_derivedType' if the constructor not already registered. + const auto& updateIndex = [&](std::size_t pIndex)->void { + ctorSet.insert(std::make_pair(hashKey, pIndex)); + }; + + //will be called from '_derivedType' to check if the constructor already registered. + const auto& getIndex = [&]()-> std::size_t { + const auto& itr = ctorSet.find(hashKey); + return (itr != ctorSet.end() ? itr->second : index_none); + }; + + //add the lambda in 'FunctorContainer'. + std::size_t index = _derivedType::pushBack(getCopyConstructorCaller<_recordType, _signature...>(), getIndex, updateIndex); + const auto& signatureStr = _derivedType::template getSignatureStr<_recordType>(true); + return detail::FunctorId(index, recordId, recordId, containerId, signatureStr); } } \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/SetupFunction.h b/ReflectionTemplateLib/detail/inc/SetupFunction.h index 32080305..493aa4a9 100644 --- a/ReflectionTemplateLib/detail/inc/SetupFunction.h +++ b/ReflectionTemplateLib/detail/inc/SetupFunction.h @@ -1,3 +1,14 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include "FunctorId.h" @@ -13,7 +24,7 @@ namespace rtl { * deriving classes is FunctorContainer<...>, which must implement - - std::size_t& _derived::getContainerId(); - std::string _derivedType::getSignatureStr(); - - std::size_t& _derived::pushBack(std::function < access::RStatus(_signature...) >, + - std::size_t& _derived::pushBack(std::function, std::function, std::function); * sets up only non-member or static-member-function functors in table. @@ -21,10 +32,19 @@ namespace rtl { */ template class SetupFunction { + template + using FunctionLambda = std::function < Return(_signature...) >; + + template + static FunctionLambda<_signature...> getCaller(void(*pFunctor)(_signature...)); + + template + static FunctionLambda<_signature...> getCaller(_returnType(*pFunctor)(_signature...)); + protected: template - static const detail::FunctorId addFunctor(_returnType(*pFunctor)(_signature...)); + static const detail::FunctorId addFunctor(_returnType(*pFunctor)(_signature...), std::size_t pRecordId); }; } } \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/SetupFunction.hpp b/ReflectionTemplateLib/detail/inc/SetupFunction.hpp index ad6aee7b..07b9931d 100644 --- a/ReflectionTemplateLib/detail/inc/SetupFunction.hpp +++ b/ReflectionTemplateLib/detail/inc/SetupFunction.hpp @@ -1,11 +1,71 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#pragma once -#include "RStatus.h" #include "SetupFunction.h" +#include "RObjectBuilder.hpp" namespace rtl { namespace detail { + template + template + inline SetupFunction<_derivedType>::FunctionLambda<_signature...> + SetupFunction<_derivedType>::getCaller(void(*pFunctor)(_signature...)) + { + return [pFunctor](_signature&&... params) -> Return + { + pFunctor(std::forward<_signature>(params)...); + return { error::None, RObject{} }; + }; + } + + + template + template + inline SetupFunction<_derivedType>::FunctionLambda<_signature...> + SetupFunction<_derivedType>::getCaller(_returnType(*pFunctor)(_signature...)) + { + /* a variable arguments lambda, which finally calls the 'pFunctor' with 'params...'. + this is stored in _derivedType's (FunctorContainer) vector holding lambda's. + */ return [pFunctor](_signature&&...params)-> Return + { + constexpr bool isConstCastSafe = (!traits::is_const_v<_returnType>); + + if constexpr (std::is_reference_v<_returnType>) { + /* if the function returns reference, this block will be retained by compiler. + Note: reference to temporary or dangling is not checked here. + */ using _rawRetType = traits::raw_t<_returnType>; + const _rawRetType& retObj = pFunctor(std::forward<_signature>(params)...); + return { error::None, + RObjectBuilder::template + build(&retObj, rtl::index_none, isConstCastSafe) + }; + } + else { + //if the function returns anything (not refrence), this block will be retained by compiler. + auto&& retObj = pFunctor(std::forward<_signature>(params)...); + using T = std::remove_cvref_t; + + return { error::None, + RObjectBuilder::template + build(std::forward(retObj), rtl::index_none, isConstCastSafe) + }; + } + }; + } + + /* @method: addFunctor(). @param: 'pFuntor' (a non-member or static-member function pointer). '_derivedType' : class deriving this class ('FunctionContainer<...>'). @@ -16,23 +76,22 @@ namespace rtl * thread safe, multiple functors can be registered simultaneously. */ template template - inline const detail::FunctorId SetupFunction<_derivedType>::addFunctor(_returnType(*pFunctor)(_signature...)) + inline const detail::FunctorId SetupFunction<_derivedType>::addFunctor(_returnType(*pFunctor)(_signature...), std::size_t pRecordId) { - /* set of already registered functors. (static life time). used std::vector, since std::set/map are not designed for function pointers */ static std::vector> functorSet; /* adds the generated functor index to the 'functorSet'. (thread safe). called from '_derivedType' ('FunctorContainer') - */ const auto& updateIndex = [&](const std::size_t& pIndex) + */ const auto& updateIndex = [&](std::size_t pIndex)->void { functorSet.emplace_back(pFunctor, pIndex); }; /* checks if the 'pFunctor' is already present in 'functorSet'. (thread safe). called from '_derivedType' ('FunctorContainer') - */ const auto& getIndex = [&]()->const std::size_t + */ const auto& getIndex = [&]()-> std::size_t { //linear search, efficient for small set. for (const auto& fptr : functorSet) { @@ -42,39 +101,19 @@ namespace rtl } } //functor is not already registered, return '-1'. - return -1; + return rtl::index_none; }; //generate a type-id of '_returnType'. - const auto& retTypeId = TypeId<_returnType>::get(); - - /* a variable arguments lambda, which finally calls the 'pFunctor' with 'params...'. - this is stored in _derivedType's (FunctorContainer) vector holding lambda's. - */ const auto functor = [=](_signature...params)->access::RStatus - { - //if functor does not returns anything, this 'if' block is retained and else block is omitted by compiler. - if constexpr (std::is_same_v<_returnType, void>) { - - //call will definitely be successful, since the signature type has alrady been validated. - (*pFunctor)(params...); - return access::RStatus(Error::None); - } - //if functor returns value, this 'else' block is retained and 'if' block is omitted by compiler. - else { - //call will definitely be successful, since the signature type has alrady been validated. - const _returnType& retObj = (*pFunctor)(params...); - const TypeQ& qualifier = std::is_const<_returnType>::value ? TypeQ::Const : TypeQ::Mute; - //return 'RStatus' with return value wrapped in it as std::any. - return access::RStatus(std::make_any<_returnType>(retObj), retTypeId, qualifier); - } - }; - + const std::size_t retTypeId = TypeId>::get(); //finally add the lambda 'functor' in 'FunctorContainer' lambda vector and get the index. - const std::size_t& index = _derivedType::pushBack(functor, getIndex, updateIndex); + const std::size_t index = _derivedType::pushBack(getCaller(pFunctor), getIndex, updateIndex); //construct the hash-key 'FunctorId' and return. - return detail::FunctorId(index, retTypeId, TypeId<>::None, _derivedType::getContainerId(), - _derivedType::template getSignatureStr<_returnType>()); + return detail::FunctorId{ + index, retTypeId, pRecordId, _derivedType::getContainerId(), + _derivedType::template getSignatureStr<_returnType>() + }; } } } \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/SetupMethod.h b/ReflectionTemplateLib/detail/inc/SetupMethod.h index 88fd55c0..ee521194 100644 --- a/ReflectionTemplateLib/detail/inc/SetupMethod.h +++ b/ReflectionTemplateLib/detail/inc/SetupMethod.h @@ -1,3 +1,14 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include "FunctorId.h" @@ -10,25 +21,40 @@ namespace rtl { @param: _derivedType (type which inherits this class) * creates a lambda to perform call on the registered functor. * adds it to the functor-container, maintains the already added functor set as well. - * deriving classes is MethodContainer & - MethodContainer, which must implement - + * deriving classes is MethodContainer & + MethodContainer, which must implement - - std::size_t& _derived::getContainerId(); - std::string _derivedType::getSignatureStr(); - - std::size_t& _derived::pushBack(std::function < access::RStatus(_signature...) >, + - std::size_t& _derived::pushBack(std::function < RObject (error&, const rtl::RObject&, _signature...) >, std::function, std::function); * sets up only non-static-member-function functors in lambda table. * called from 'ReflectionBuilder', as _derivedType member. */ template - class SetupMethod - { - protected: + class SetupMethod + { + template + using MethodLambda = std::function < Return(const rtl::RObject&, _signature...) >; + + template + static MethodLambda<_signature...> getMethodCaller(_returnType(_recordType::* pFunctor)(_signature...)); + + template + static MethodLambda<_signature...> getMethodCaller(_returnType(_recordType::* pFunctor)(_signature...) const); + + template + static MethodLambda<_signature...> getMethodCaller(void(_recordType::* pFunctor)(_signature...)); + + template + static MethodLambda<_signature...> getMethodCaller(void(_recordType::* pFunctor)(_signature...) const); + + protected: - template - static const detail::FunctorId addFunctor(_retType(_recordType::* pFunctor)(_signature...)); + template + static const detail::FunctorId addFunctor(_returnType(_recordType::* pFunctor)(_signature...)); - template - static const detail::FunctorId addFunctor(_retType(_recordType::* pFunctor)(_signature...) const); - }; - } + template + static const detail::FunctorId addFunctor(_returnType(_recordType::* pFunctor)(_signature...) const); + }; + } } \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/SetupMethod.hpp b/ReflectionTemplateLib/detail/inc/SetupMethod.hpp index afc1f219..78979dae 100644 --- a/ReflectionTemplateLib/detail/inc/SetupMethod.hpp +++ b/ReflectionTemplateLib/detail/inc/SetupMethod.hpp @@ -1,39 +1,166 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once -#include "RStatus.h" -#include "TypeId.hpp" +#include "view.h" +#include "TypeId.h" #include "SetupMethod.h" +#include "RObjectBuilder.hpp" namespace rtl { namespace detail { - /* @method: addFunctor(). + + template + template + inline SetupMethod<_derivedType>::MethodLambda<_signature...> + SetupMethod<_derivedType>::getMethodCaller(void(_recordType::* pFunctor)(_signature...)) + { + /* a variable arguments lambda, which finally calls the 'pFunctor' with 'params...'. + this is stored in _derivedType's (MethodContainer) vector holding lambda's. + */ return [pFunctor](const RObject& pTargetObj, _signature&&...params)-> Return + { + if (!pTargetObj.isConstCastSafe()) [[unlikely]] { + return { error::IllegalConstCast, RObject{} }; + } + + _recordType& target = const_cast<_recordType&>(pTargetObj.view<_recordType>()->get()); + (target.*pFunctor)(std::forward<_signature>(params)...); + return { error::None, RObject{} }; + }; + } + + + template + template + inline SetupMethod<_derivedType>::MethodLambda<_signature...> + SetupMethod<_derivedType>::getMethodCaller(_returnType(_recordType::* pFunctor)(_signature...)) + { + /* a variable arguments lambda, which finally calls the 'pFunctor' with 'params...'. + this is stored in _derivedType's (MethodContainer) vector holding lambda's. + */ return [pFunctor](const RObject& pTargetObj, _signature&&...params)-> Return + { + if (!pTargetObj.isConstCastSafe()) [[unlikely]] { + return { error::IllegalConstCast, RObject{} }; + } + + constexpr bool isConstCastSafe = (!traits::is_const_v<_returnType>); + //'target' needs const_cast, since the functor is non-const-member-function. + _recordType& target = const_cast<_recordType&>(pTargetObj.view<_recordType>()->get()); + if constexpr (std::is_reference_v<_returnType>) + { + /* if the function returns reference, this block will be retained by compiler. + Note: reference to temporary or dangling is not checked here. + */ using _rawRetType = traits::raw_t<_returnType>; + const _rawRetType& retObj = (target.*pFunctor)(std::forward<_signature>(params)...); + return { error::None, + RObjectBuilder::template + build(&retObj, rtl::index_none, isConstCastSafe) + }; + } + else { + + auto&& retObj = (target.*pFunctor)(std::forward<_signature>(params)...); + using T = std::remove_cvref_t; + + return { error::None, + RObjectBuilder::template + build(std::forward(retObj), rtl::index_none, isConstCastSafe) + }; + } + }; + } + + + + template + template + inline SetupMethod<_derivedType>::MethodLambda<_signature...> + SetupMethod<_derivedType>::getMethodCaller(void(_recordType::* pFunctor)(_signature...) const) + { + /* a variable arguments lambda, which finally calls the 'pFunctor' with 'params...'. + this is stored in _derivedType's (MethodContainer) vector holding lambda's. + */ return [pFunctor](const RObject& pTargetObj, _signature&&...params)-> Return + { + const _recordType& target = pTargetObj.view<_recordType>()->get(); + (target.*pFunctor)(std::forward<_signature>(params)...); + return { error::None, RObject{} }; + }; + } + + + template + template + inline SetupMethod<_derivedType>::MethodLambda<_signature...> + SetupMethod<_derivedType>::getMethodCaller(_returnType(_recordType::* pFunctor)(_signature...) const) + { + /* a variable arguments lambda, which finally calls the 'pFunctor' with 'params...'. + this is stored in _derivedType's (MethodContainer) vector holding lambda's. + */ return [pFunctor](const RObject& pTargetObj, _signature&&...params)-> Return + { + constexpr bool isConstCastSafe = (!traits::is_const_v<_returnType>); + //'target' is const and 'pFunctor' is const-member-function. + const _recordType& target = pTargetObj.view<_recordType>()->get(); + if constexpr (std::is_reference_v<_returnType>) { + /* if the function returns reference, this block will be retained by compiler. + Note: reference to temporary or dangling is not checked here. + */ using _rawRetType = traits::raw_t<_returnType>; + const _rawRetType& retObj = (target.*pFunctor)(std::forward<_signature>(params)...); + return { error::None, + RObjectBuilder::template + build(&retObj, rtl::index_none, isConstCastSafe) + }; + } + else { + + auto&& retObj = (target.*pFunctor)(std::forward<_signature>(params)...); + using T = std::remove_cvref_t; + + return { error::None, + RObjectBuilder::template + build(std::forward(retObj), rtl::index_none, isConstCastSafe) + }; + } + }; + } + + + /* @method: addFunctor(). @param: 'pFuntor' (a non-const, non-static-member function pointer). - '_derivedType' : class deriving this class ('MethodContainer'). + '_derivedType' : class deriving this class ('MethodContainer'). '_recordType' : the owner 'class/stuct' type of the functor. '_returnType' : return type deduced from 'pFunctor'. '_signature...' : function signature deduced from 'pFunctor'. @return: 'FunctorId' object, a hash-key to lookup the lambda (functor-wrapped) in the _derivedType's lambda-table. - * adds lambda (functor-wrapped) in '_derivedType' (MethodContainer) and maintains functorSet. + * adds lambda (functor-wrapped) in '_derivedType' (MethodContainer) and maintains functorSet. * thread safe, multiple functors can be registered simultaneously. */ template - template - inline const detail::FunctorId SetupMethod<_derivedType>::addFunctor(_retType(_recordType::* pFunctor)(_signature...)) + template + inline const detail::FunctorId SetupMethod<_derivedType>::addFunctor(_returnType(_recordType::* pFunctor)(_signature...)) { /* set of already registered functors. (static life time). used std::vector, efficient for small sets. std::set/map will be overhead. */ static std::vector> functorSet; /* adds the generated functor index to the 'functorSet'. (thread safe). - called from '_derivedType' (MethodContainer) - */ const auto& updateIndex = [&](const std::size_t& pIndex) { + called from '_derivedType' (MethodContainer) + */ const auto& updateIndex = [&](std::size_t pIndex)->void { functorSet.emplace_back(pFunctor, pIndex); }; /* checks if the 'pFunctor' is already present in 'functorSet'. (thread safe). called from '_derivedType' ('FunctorContainer') - */ const auto& getIndex = [&]()->const std::size_t + */ const auto& getIndex = [&]()->std::size_t { //linear search, efficient for small set. for (const auto& fptr : functorSet) { @@ -43,69 +170,57 @@ namespace rtl } } //functor is not already registered, return '-1'. - return -1; + return index_none; }; //generate a type-id of '_returnType'. - const std::size_t retTypeId = TypeId<_retType>::get(); - - /* a variable arguments lambda, which finally calls the 'pFunctor' with 'params...'. - this is stored in _derivedType's (MethodContainer) vector holding lambda's. - */ const auto functor = [=](const std::any& pTargetObj, _signature...params)->access::RStatus - { - //cast would not fail, since the type has already been validated. - _recordType* target = std::any_cast<_recordType*>(pTargetObj); - - //if functor does not returns anything, this 'if' block is retained and else block is omitted by compiler. - if constexpr (std::is_same_v<_retType, void>) { - //call will definitely be successful, since the object type, signature type has already been validated. - (target->*pFunctor)(params...); - return access::RStatus(Error::None); - } - //if functor returns value, this 'else' block is retained and 'if' block is omitted by compiler. - else { - - //call will definitely be successful, since the object type, signature type has already been validated. - const _retType& retObj = (target->*pFunctor)(params...); - const TypeQ& qualifier = std::is_const<_retType>::value ? TypeQ::Const : TypeQ::Mute; - - //return 'RStatus' with return value wrapped in it as std::any. - return access::RStatus(std::make_any<_retType>(retObj), retTypeId, qualifier); - } - }; + const std::size_t retTypeId = TypeId>::get(); + //finally add the lambda 'functor' in 'MethodContainer' lambda vector and get the index. - //finally add the lambda 'functor' in 'MethodContainer' lambda vector and get the index. - const std::size_t& index = _derivedType::pushBack(functor, getIndex, updateIndex); - - //construct the hash-key 'FunctorId' and return. - return detail::FunctorId(index, retTypeId, TypeId<_recordType>::get(), _derivedType::getContainerId(), - _derivedType::template getSignatureStr<_recordType, _retType>()); + if constexpr (std::is_same_v<_returnType, void>) + { + const std::size_t index = _derivedType::pushBack(getMethodCaller(pFunctor), getIndex, updateIndex); + //construct the hash-key 'FunctorId' and return. + return detail::FunctorId{ + index, retTypeId, TypeId<_recordType>::get(), _derivedType::getContainerId(), + _derivedType::template getSignatureStr<_recordType, _returnType>() + }; + } + else + { + const std::size_t index = _derivedType::pushBack(getMethodCaller(pFunctor), getIndex, updateIndex); + //construct the hash-key 'FunctorId' and return. + return detail::FunctorId { + index, retTypeId, TypeId<_recordType>::get(), _derivedType::getContainerId(), + _derivedType::template getSignatureStr<_recordType, _returnType>() + }; + } } /* @method: addFunctor(). @param: 'pFuntor' (a const, non-static-member function pointer). - '_derivedType' : class deriving this class ('MethodContainer'). + '_derivedType' : class deriving this class ('MethodContainer'). '_recordType' : the owner 'class/stuct' type of the functor. '_returnType' : return type deduced from 'pFunctor'. '_signature...' : function signature deduced from 'pFunctor'. @return: 'FunctorId' object, a hash-key to lookup the lambda (containing functor) in the _derivedType's lambda table. - * adds lambda (containing functor) in '_derivedType' (MethodContainer) and maintains a functorSet. + * adds lambda (containing functor) in '_derivedType' (MethodContainer) and maintains a functorSet. * thread safe, multiple functors can be registered simultaneously. */ template - template - inline const detail::FunctorId SetupMethod<_derivedType>::addFunctor(_retType(_recordType::* pFunctor)(_signature...) const) + template + inline const detail::FunctorId SetupMethod<_derivedType>::addFunctor(_returnType(_recordType::* pFunctor)(_signature...) const) { /* set of already registered functors. (static life time). used std::vector, efficient for small sets. std::set/map will be overhead. */ static std::vector> functorSet; - const auto& updateIndex = [&](const std::size_t& pIndex) { + const auto& updateIndex = [&](std::size_t pIndex)->void { functorSet.emplace_back(pFunctor, pIndex); - }; + }; /* adds the generated functor index to the 'functorSet'. (thread safe). - called from '_derivedType' (MethodContainer) - */ const auto& getIndex = [&]()->const std::size_t + called from '_derivedType' (MethodContainer) + */ const auto& getIndex = [&]()->std::size_t { //linear search, efficient for small set. for (const auto& fptr : functorSet) { @@ -115,43 +230,31 @@ namespace rtl } } //functor is not already registered, return '-1'. - return -1; + return index_none; }; - //generate a type-id of '_retType'. - const std::size_t retTypeId = TypeId<_retType>::get(); + //generate a type-id of '_returnType'. + const std::size_t retTypeId = TypeId>::get(); + //finally add the lambda 'functor' in 'MethodContainer' lambda vector and get the index. - /* a variable arguments lambda, which finally calls the 'pFunctor' with 'params...'. - this is stored in _derivedType's (MethodContainer) vector holding lambda's. - */ const auto functor = [=](const std::any& pTargetObj, _signature...params)->access::RStatus + if constexpr (std::is_same_v<_returnType, void>) { - //cast would not fail, since the type has already been validated. - _recordType* target = std::any_cast<_recordType*>(pTargetObj); - - //if functor does not returns anything, this 'if' block is retained and else block is omitted by compiler. - if constexpr (std::is_same_v<_retType, void>) { - - //call will definitely be successful, since the object type, signature type has already been validated. - ((static_cast(target))->*pFunctor)(params...); - return access::RStatus(Error::None); - } - else { - const TypeQ& qualifier = std::is_const<_retType>::value ? TypeQ::Const : TypeQ::Mute; - - //call will definitely be successful, since the object type, signature type has already been validated. - const _retType& retObj = ((static_cast(target))->*pFunctor)(params...); - - //return 'RStatus' with return value wrapped in it as std::any. - return access::RStatus(std::make_any<_retType>(retObj), retTypeId, qualifier); - } - }; - - //finally add the lambda 'functor' in 'MethodContainer' lambda vector and get the index. - const std::size_t& index = _derivedType::pushBack(functor, getIndex, updateIndex); - - //construct the hash-key 'FunctorId' and return. - return detail::FunctorId(index, retTypeId, TypeId<_recordType>::get(), _derivedType::getContainerId(), - _derivedType::template getSignatureStr<_recordType, _retType>()); + const std::size_t index = _derivedType::pushBack(getMethodCaller(pFunctor), getIndex, updateIndex); + //construct the hash-key 'FunctorId' and return. + return detail::FunctorId { + index, retTypeId, TypeId<_recordType>::get(), _derivedType::getContainerId(), + _derivedType::template getSignatureStr<_recordType, _returnType>() + }; + } + else + { + const std::size_t index = _derivedType::pushBack(getMethodCaller(pFunctor), getIndex, updateIndex); + //construct the hash-key 'FunctorId' and return. + return detail::FunctorId { + index, retTypeId, TypeId<_recordType>::get(), _derivedType::getContainerId(), + _derivedType::template getSignatureStr<_recordType, _returnType>() + }; + } } } } \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/inc/TypeId.h b/ReflectionTemplateLib/detail/inc/TypeId.h index 12e4833e..09f4dce6 100644 --- a/ReflectionTemplateLib/detail/inc/TypeId.h +++ b/ReflectionTemplateLib/detail/inc/TypeId.h @@ -1,12 +1,26 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + #pragma once #include #include +#include namespace rtl { namespace detail { + extern std::size_t generate_unique_id(); + //class to generate unique type-id for a type or combination of types. template struct TypeId; @@ -18,15 +32,18 @@ namespace rtl { //represents '_type' or 'std::nullptr_t' for TypeId<> (empty). using HEAD = _type; - //'0' represents no type. + //'0' represents no type. [Never change, critical.] static constexpr const std::size_t None = 0; - static const std::size_t get() { - return m_typeId; + static const std::size_t get() + { + //statically initialize a unique-id. + static const std::size_t typeId = generate_unique_id(); + return typeId; } //returns the type-list as string. - static const std::string toString() + static std::string toString() { if constexpr (std::is_same_v<_type, void>) { return std::string("void"); @@ -34,14 +51,26 @@ namespace rtl { if constexpr (std::is_same_v<_type, std::string>) { return std::string("std::string"); } + if constexpr (std::is_same_v<_type, const std::string>) { + return std::string("const std::string"); + } + if constexpr (std::is_same_v<_type, std::string&>) { + return std::string("std::string&"); + } + if constexpr (std::is_same_v<_type, const std::string&>) { + return std::string("const std::string&"); + } + if constexpr (std::is_same_v<_type, std::string&&>) { + return std::string("const std::string&&"); + } if constexpr (!std::is_same_v<_type, std::nullptr_t>) { return std::string(typeid(_type).name()); } + if constexpr (std::is_same_v<_type, std::nullptr_t>) { + return "std::nullptr_t"; + } else return std::string(); } - - private: - static const std::size_t m_typeId; }; @@ -56,7 +85,7 @@ namespace rtl { using TAIL = TypeId<_rest...>; //returns the type-list as string. - static const std::string toString() + static std::string toString() { const std::string& tailStr = TAIL::toString(); if (std::is_same::value) { diff --git a/ReflectionTemplateLib/detail/inc/TypeId.hpp b/ReflectionTemplateLib/detail/inc/TypeId.hpp deleted file mode 100644 index d187dc46..00000000 --- a/ReflectionTemplateLib/detail/inc/TypeId.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -#include "TypeId.h" - -namespace rtl { - - namespace detail - { - extern std::atomic g_typeIdCounter; - - //statically initialize a unique-id. - template - const std::size_t TypeId<_type>::m_typeId = g_typeIdCounter.fetch_add(1); - } -} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/src/CMakeLists.txt b/ReflectionTemplateLib/detail/src/CMakeLists.txt index 83dc4255..46ec416a 100644 --- a/ReflectionTemplateLib/detail/src/CMakeLists.txt +++ b/ReflectionTemplateLib/detail/src/CMakeLists.txt @@ -1,17 +1,26 @@ # Create a variable containing the source files for your target set(LOCAL_SOURCES "${CMAKE_CURRENT_LIST_DIR}/CxxReflection.cpp" - "${CMAKE_CURRENT_LIST_DIR}/FunctorId.cpp" - "${CMAKE_CURRENT_LIST_DIR}/TypeIdInitializer.cpp" + "${CMAKE_CURRENT_LIST_DIR}/ReflectCast.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RObjectConverters_string.cpp" ) SET(LOCAL_HEADERS + "${PROJECT_SOURCE_DIR}/detail/inc/CallReflector.h" "${PROJECT_SOURCE_DIR}/detail/inc/CxxReflection.h" + "${PROJECT_SOURCE_DIR}/detail/inc/FunctionCaller.h" + "${PROJECT_SOURCE_DIR}/detail/inc/FunctionCaller.hpp" + "${PROJECT_SOURCE_DIR}/detail/inc/MethodInvoker.h" + "${PROJECT_SOURCE_DIR}/detail/inc/MethodInvoker.hpp" "${PROJECT_SOURCE_DIR}/detail/inc/FunctorContainer.h" "${PROJECT_SOURCE_DIR}/detail/inc/FunctorId.h" + "${PROJECT_SOURCE_DIR}/detail/inc/RObjectId.h" "${PROJECT_SOURCE_DIR}/detail/inc/MethodContainer.h" + "${PROJECT_SOURCE_DIR}/detail/inc/ReflectCast.h" + "${PROJECT_SOURCE_DIR}/detail/inc/ReflectCast.hpp" + "${PROJECT_SOURCE_DIR}/detail/inc/ReflectCastUtil.h" "${PROJECT_SOURCE_DIR}/detail/inc/ReflectionBuilder.h" "${PROJECT_SOURCE_DIR}/detail/inc/ReflectionBuilder.hpp" "${PROJECT_SOURCE_DIR}/detail/inc/SetupConstructor.h" @@ -21,7 +30,10 @@ SET(LOCAL_HEADERS "${PROJECT_SOURCE_DIR}/detail/inc/SetupMethod.h" "${PROJECT_SOURCE_DIR}/detail/inc/SetupMethod.hpp" "${PROJECT_SOURCE_DIR}/detail/inc/TypeId.h" - "${PROJECT_SOURCE_DIR}/detail/inc/TypeId.hpp" + "${PROJECT_SOURCE_DIR}/detail/inc/RObjectUPtr.h" + "${PROJECT_SOURCE_DIR}/detail/inc/RObjExtracter.h" + "${PROJECT_SOURCE_DIR}/detail/inc/RObjectBuilder.h" + "${PROJECT_SOURCE_DIR}/detail/inc/RObjectBuilder.hpp" ) diff --git a/ReflectionTemplateLib/detail/src/CxxReflection.cpp b/ReflectionTemplateLib/detail/src/CxxReflection.cpp index 536e698e..762974c7 100644 --- a/ReflectionTemplateLib/detail/src/CxxReflection.cpp +++ b/ReflectionTemplateLib/detail/src/CxxReflection.cpp @@ -1,3 +1,16 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#include +#include #include "TypeId.h" #include "Record.h" @@ -12,28 +25,15 @@ namespace rtl { @params: 'const std::vector&' * recieves vector of 'Function' objects, forwarded from 'CxxMirror' constructor. * initiates grouping of each 'Function' object under namespace, class/struct. - */ CxxReflection::CxxReflection(const std::vector& pFunctions) + */ CxxReflection::CxxReflection(const std::vector& pFunctions) { - for (const auto& function : pFunctions) { - organizeFunctorsMetaData(function); - } - } - - - /* @method: addRecord - @params: RecordMap, Function - * constructs the 'Record'(class/struct) object & adds 'Function' as 'Method' to it. - * if the 'Record' already exists in the map, the 'Function' object is added as 'Method' to it. - */ void CxxReflection::addRecord(RecordMap& pRecordMap, const access::Function& pFunction) - { - const auto& recordName = pFunction.getRecordName(); - const auto& itr = pRecordMap.find(recordName); - if (itr == pRecordMap.end()) { - const auto& recordItr = pRecordMap.emplace(recordName, access::Record(recordName)); - addMethod(recordItr.first->second.getFunctionsMap(),pFunction); - } - else { - addMethod(itr->second.getFunctionsMap(), pFunction); + buildRecordIdMap(pFunctions); + for (const auto& function : pFunctions) + { + if (validateFunctionByRecordId(function) && !insertFunctionToRecordIdMap(function)) + { + insertFunctionToNamespaceMap(function); + } } } @@ -41,7 +41,7 @@ namespace rtl { /* @method: addFunction @params: FunctionMap, Function * adds the 'Function' object as non-member function mapped to the given namespace name. - */ void CxxReflection::addFunction(FunctionMap& pFunctionMap, const access::Function& pFunction) + */ void CxxReflection::addFunction(FunctionMap& pFunctionMap, const Function& pFunction) { const auto& fname = pFunction.getFunctionName(); const auto& itr = pFunctionMap.find(fname); @@ -60,30 +60,15 @@ namespace rtl { @params: MethodMap, Function * adds the 'Function' object as 'Method' object in MethodMap, contained by 'Record' object. * if the function name already exists in the map, then 'FunctorId' from the param 'pFunction' is added to already existing 'Function'. - * if a 'Function' object represents a Constructor, it might have the destructor 'FunctorId' as well. - * if destructor 'FunctorId' is found, destructor 'Function' object is created and added to the 'MethodMap'. - */ void CxxReflection::addMethod(MethodMap& pMethodMap, const access::Function& pFunction) + * if a 'Function' object represents a Constructor, it might have the copy-constructor 'FunctorId' as well. + * if copy-constructor 'FunctorId' is found, 'Function' object is created and added to the 'MethodMap' for the same. + */ void CxxReflection::addMethod(MethodMap& pMethodMap, const Function& pFunction) { const auto& fname = pFunction.getFunctionName(); const auto& itr = pMethodMap.find(fname); - if (itr == pMethodMap.end()) - { - auto& functorIds = pFunction.getFunctorIds(); - /* This condition will be true only in case that 'Function' object represents a constructor - and has more than one 'FunctorId'. every other function registered will have only one 'FunctorId'. - */ if (functorIds.size() > 1) - { - const auto& dctorName = CtorName::dctor(pFunction.getRecordName()); - if (pMethodMap.find(dctorName) == pMethodMap.end()) { - //destructor 'FunctorId' will always be the second in the constructor's FunctorId's vector. - access::Method method = access::Method::getDestructorMethod(pFunction, functorIds[1]); - pMethodMap.insert(std::make_pair(method.getFunctionName(), method)); - } - //remove the destructor 'FunctorId' from the constructor's 'FunctorId' vector. - functorIds.pop_back(); - } + if (itr == pMethodMap.end()) { //construct 'Method' obejct and add. - pMethodMap.emplace(fname, access::Method(pFunction)); + pMethodMap.emplace(fname, Method(pFunction)); } else { const auto& function = itr->second; @@ -96,32 +81,133 @@ namespace rtl { /* @method: organizeFunctorsMetaData @params: Function * seggregates all the 'Function' objects and builds 'Record' & 'Method' objects. - */ void CxxReflection::organizeFunctorsMetaData(const access::Function& pFunction) + */ void CxxReflection::insertFunctionToNamespaceMap(const Function& pFunction) { - const auto& nameSpace = pFunction.getNamespace(); - - //if the record-name is empty, 'Function' object is considered as non-member function. - if (pFunction.getRecordName().empty()) { - const auto& itr = m_nsFunctionsMap.find(nameSpace); - if (itr == m_nsFunctionsMap.end()) { - const auto& funcMapItr = m_nsFunctionsMap.emplace(nameSpace, FunctionMap()); + const std::string& nameSpace = pFunction.getNamespace(); + const std::string& recordName = pFunction.getRecordName(); + const std::size_t recordId = pFunction.getRecordTypeId(); + //if the recordId(class/struct's type-id) is TypeId<>::None, 'Function' object is considered as non-member function. + if (recordId == TypeId<>::None) + { + const auto& itr = m_functionNamespaceMap.find(nameSpace); + if (itr == m_functionNamespaceMap.end()) { + const auto& funcMapItr = m_functionNamespaceMap.emplace(nameSpace, FunctionMap()); addFunction(funcMapItr.first->second, pFunction); } else { addFunction(itr->second, pFunction); } } - //if the record-name is not-empty, 'Function' object is considered as member function, a 'Method'. - else { - const auto& itr = m_nsRecordsMap.find(nameSpace); - if (itr == m_nsRecordsMap.end()) { - const auto& recordMapItr = m_nsRecordsMap.emplace(nameSpace, RecordMap()); - addRecord(recordMapItr.first->second, pFunction); + } + + + void CxxReflection::addInNamespaceMap(Record& pRecord) + { + const auto& itr = m_recordNamespaceMap.find(pRecord.m_namespace); + if (itr == m_recordNamespaceMap.end()) + { + RecordMap& recordStrMap = m_recordNamespaceMap.emplace(pRecord.m_namespace, RecordMap()).first->second; + recordStrMap.emplace(pRecord.m_recordName, std::ref(pRecord)); + } + else + { + RecordMap& recordStrMap = itr->second; + const auto& itr0 = recordStrMap.find(pRecord.m_recordName); + if (itr0 == recordStrMap.end()) { + recordStrMap.emplace(pRecord.m_recordName, std::ref(pRecord)); + } + } + } + + + void CxxReflection::buildRecordIdMap(const std::vector& pFunctions) + { + for (auto& function : pFunctions) { + + const auto& recordName = function.getRecordName(); + const std::size_t recordId = function.getRecordTypeId(); + const bool isCtorOverload = (function.getFunctionName() == ctor_name()); + if (recordId != TypeId<>::None && (isCtorOverload || !recordName.empty())) + { + const auto& itr = m_recordIdMap.find(recordId); + if (itr == m_recordIdMap.end()) { + + auto& record = m_recordIdMap.emplace(recordId, Record(recordName, recordId, function.m_namespace)).first->second; + addMethod(record.getFunctionsMap(), function); + addInNamespaceMap(record); + } + else if (isCtorOverload) { + + const Record& record = itr->second; + Function constructor = function; + + constructor.m_record = record.m_recordName; + constructor.m_namespace = record.m_namespace; + constructor.m_function = ctor_name(record.m_recordName); + addMethod(record.getFunctionsMap(), constructor); + } + else { + std::cout << "\n[WARNING] Multiple registrations of the same type detected." + << "\n Type already registered as \"" << itr->second.m_recordName << "\"" + << "\n Attempted re-registration as \"" << function.getRecordName() << "\"" + << "\n This registration is ignored.\n"; + } + } + } + } + + + /* During registration of a method using: + * type().ns("std").record("string").methodConst("empty").build(&std::string::empty), + * the `givenRecordId` is generated by the `record()` call (e.g., for `std::string`), + * and the `actualRecordId` is extracted from the type of the function pointer passed to `build(...)`. + * + * - If the function is a non-member function, both `givenRecordId` and `actualRecordId` are zero (rtl::TypeId<>::None). + * - If it's a static member function, both IDs are equal, and no further validation is needed. + * - If it's a non-static member function, both IDs **must** match - this check helps catch registration errors + * where the member function belongs to a different class than the one being registered. + * + * Example of incorrect usage (caught by this validation): + * type().ns("std").record("string").methodConst("empty").build(&std::string::empty); + * Here, the record is being created for `std::string_view`, but the method pointer belongs to `std::string`. + */ const bool CxxReflection::validateFunctionByRecordId(const Function& pFunction) + { + const std::size_t givenRecordId = pFunction.getRecordTypeId(); + const std::size_t actualRecordId = pFunction.getFunctorIds()[0].getRecordId(); //Index 0 is always guaranteed to reference a valid functor. + if (givenRecordId != actualRecordId) { + std::cout << "\n[WARNING] Member function pointer does not belong to the class being registered." + << "\n Member function: " << pFunction.getFunctionName() << "(" << pFunction.getFunctorIds()[0].getSignatureStr() << ")" + << "\n This function is ignored and not registered.\n"; + return false; + } + return true; + } + + + bool CxxReflection::insertFunctionToRecordIdMap(const Function& pFunction) + { + const std::size_t recordId = pFunction.getRecordTypeId(); + if (recordId != TypeId<>::None && pFunction.m_record.empty() && pFunction.m_function != ctor_name()) + { + const auto& itr = m_recordIdMap.find(recordId); + if (itr != m_recordIdMap.end()) { + + const auto& record = itr->second; + Function memberFunc = pFunction; + + memberFunc.m_record = record.m_recordName; + memberFunc.m_namespace = record.m_namespace; + addMethod(record.getFunctionsMap(), memberFunc); } else { - addRecord(itr->second, pFunction); + std::cout << "\n[WARNING] The class/struct for this member-function is not registered." + << "\n While registering \"" << pFunction.m_function << "\"" + << "\n Make sure to register the 'Type' (struct/class) as well." + << "\n This registration is ignored.\n"; } + return true; } + return false; } } } \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/src/FunctorId.cpp b/ReflectionTemplateLib/detail/src/FunctorId.cpp deleted file mode 100644 index 72ecc1f3..00000000 --- a/ReflectionTemplateLib/detail/src/FunctorId.cpp +++ /dev/null @@ -1,26 +0,0 @@ - -#include "FunctorId.h" - -namespace rtl -{ - namespace detail - { - /* @method: getHashCode() - @return: std::size_t (a unique hash-code for a functor) - * 'm_containerId' will be same for functors(non-member) with same signatures. - * for member functions, a functor will have three atrributes - - signature - - whether it is const or non-const - - class/struct type - 'm_containerId' will be same for functors with same above attributes. - * every functor will have a distinct index in the functor-wrapped-lambda-table. - * so, combination of m_containerId & m_index is unique for every functor. - */ std::size_t FunctorId::getHashCode() const - { - return std::stoull(std::to_string(m_containerId) + - std::to_string(m_index) + - std::to_string(m_recordId) + - std::to_string(m_returnId)); - } - } -} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/src/RObjectConverters_string.cpp b/ReflectionTemplateLib/detail/src/RObjectConverters_string.cpp new file mode 100644 index 00000000..8e8363a3 --- /dev/null +++ b/ReflectionTemplateLib/detail/src/RObjectConverters_string.cpp @@ -0,0 +1,63 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#include "TypeId.h" +#include "ReflectCast.hpp" + +#include + +namespace rtl::detail +{ + template<> + template<> + void ReflectCast::pushConversion() + { + const auto& conversion = [](const std::any& pSrc, const EntityKind& pSrcEntityKind, EntityKind& pNewEntityKind)-> std::any + { + pNewEntityKind = EntityKind::Ptr; + const auto& isPtr = (pSrcEntityKind == EntityKind::Ptr); + const auto& srcObj = (isPtr ? *std::any_cast(pSrc) : std::any_cast(pSrc)); + return std::any(srcObj.c_str()); + }; + conversions().emplace_back(std::pair(TypeId::get(), conversion)); + } + + + template<> + template<> + void ReflectCast::pushConversion() + { + const auto& conversion = [](const std::any& pSrc, const EntityKind& pSrcEntityKind, EntityKind& pNewEntityKind)-> std::any + { + pNewEntityKind = EntityKind::Ptr; + const auto& isPtr = (pSrcEntityKind == EntityKind::Ptr); + const auto& srcObj = (isPtr ? *std::any_cast(pSrc) : std::any_cast(pSrc)); + return std::any(srcObj.data()); + }; + conversions().emplace_back(std::pair(TypeId::get(), conversion)); + } + + + template<> + template<> + void ReflectCast::pushConversion() + { + using _toType = std::string; + const auto& conversion = [](const std::any& pSrc, const EntityKind& pSrcEntityKind, EntityKind& pNewEntityKind)-> std::any + { + pNewEntityKind = EntityKind::Value; + const auto& isPtr = (pSrcEntityKind == EntityKind::Ptr); + const auto& srcObj = (isPtr ? *std::any_cast(pSrc) : std::any_cast(pSrc)); + return std::any(_toType(srcObj)); + }; + conversions().emplace_back(std::pair(TypeId<_toType>::get(), conversion)); + } +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/src/ReflectCast.cpp b/ReflectionTemplateLib/detail/src/ReflectCast.cpp new file mode 100644 index 00000000..0fb9e829 --- /dev/null +++ b/ReflectionTemplateLib/detail/src/ReflectCast.cpp @@ -0,0 +1,52 @@ +/************************************************************************* + * * + * Reflection Template Library (RTL) - Modern C++ Reflection Framework * + * https://github.com/ReflectCxx/ReflectionTemplateLibrary-CPP * + * * + * Copyright (c) 2025 Neeraj Singh * + * SPDX-License-Identifier: MIT * + * * + *************************************************************************/ + + +#include "ReflectCast.hpp" +#include "ReflectCastUtil.h" + +namespace rtl::detail +{ + template<> + template<> + void ReflectCast::pushConversion(); + + template<> + template<> + void ReflectCast::pushConversion(); + + template<> + template<> + void ReflectCast::pushConversion(); +} + + +namespace rtl::detail +{ + void ReflectedConversions::init() + { + static const bool _= []() + { + ReflectCast::pushConversion(); + ReflectCast::pushConversion(); + + ReflectCast::pushConversion(); + ReflectCast::pushConversion(); + + using _safePODTypes = std::tuple + ; + + auto conversions = make_conversion_pairs<_safePODTypes>(); + register_all_conversions(); + + return true; + }(); + } +} \ No newline at end of file diff --git a/ReflectionTemplateLib/detail/src/TypeIdInitializer.cpp b/ReflectionTemplateLib/detail/src/TypeIdInitializer.cpp deleted file mode 100644 index 1040c84c..00000000 --- a/ReflectionTemplateLib/detail/src/TypeIdInitializer.cpp +++ /dev/null @@ -1,17 +0,0 @@ - -#include - -#include "TypeId.h" -#include "ReflectionBuilder.h" - -namespace rtl -{ - namespace detail - { - //type id counter, statically initializes a unique-id to TypeId<...>. - std::atomic g_typeIdCounter = TypeId<>::None + 1; - - //type id counter, statically initializes a unique-id to FunctorContainer<...> and MethodContainer<...>. - std::atomic g_containerIdCounter = TypeId<>::None + 1; - } -} \ No newline at end of file diff --git a/run_benchmarks.sh b/run_benchmarks.sh new file mode 100755 index 00000000..f2f11c4d --- /dev/null +++ b/run_benchmarks.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Config +BINARY="./bin/RTLBenchmarkApp" +LOGFILE="./benchmark_runs.log" + +# Clear old log file +: > "$LOGFILE" + +echo "Starting benchmark runs..." | tee -a "$LOGFILE" +echo "Binary: $BINARY" | tee -a "$LOGFILE" +echo "Log: $LOGFILE" | tee -a "$LOGFILE" +echo "===================================" | tee -a "$LOGFILE" + +# First handle scale=0, 10 times +SCALE=0 +for i in $(seq 1 10); do + echo ">>> Run $i: workload scale = $SCALE" | tee -a "$LOGFILE" + "$BINARY" "$SCALE" >> "$LOGFILE" 2>&1 + echo "-----------------------------------" | tee -a "$LOGFILE" +done + +# Now handle scales 25, 50, ... 200, each 5 times +for SCALE in $(seq 25 25 200); do + for i in $(seq 1 5); do + echo ">>> Run $i: workload scale = $SCALE" | tee -a "$LOGFILE" + "$BINARY" "$SCALE" >> "$LOGFILE" 2>&1 + echo "-----------------------------------" | tee -a "$LOGFILE" + done +done + +echo "All benchmarks completed." | tee -a "$LOGFILE" diff --git a/text-benchmark-logs/benchmark_return_string_view.log b/text-benchmark-logs/benchmark_return_string_view.log new file mode 100644 index 00000000..1acdfca7 --- /dev/null +++ b/text-benchmark-logs/benchmark_return_string_view.log @@ -0,0 +1,1655 @@ +Starting benchmark runs... +Binary: ./bin/RTLBenchmarkApp +Log: ./benchmark_runs.log +=================================== +>>> Run 1: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:26:58+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3673.84 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.50, 0.43, 0.62 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.68 ns 2.68 ns 261129948 +StdFuncCall::noReturn 3.63 ns 3.63 ns 205756521 +ReflectedCall::noReturn 4.56 ns 4.56 ns 152287993 + +StdFuncMethodCall::noReturn 3.66 ns 3.66 ns 190424278 +ReflectedMethodCall::noReturn 7.58 ns 7.58 ns 91861976 +-------------------------------------------------------------------------- +DirectCall::withReturn 7.99 ns 7.99 ns 87640884 +StdFuncCall::withReturn 8.19 ns 8.19 ns 85275679 +ReflectedCall::withReturn 16.4 ns 16.4 ns 42162218 + +StdFuncMethodCall::withReturn 8.19 ns 8.19 ns 85314435 +ReflectedMethodCall::withReturn 18.4 ns 18.4 ns 38123916 +----------------------------------- +>>> Run 2: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:27:07+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4900 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.58, 0.45, 0.63 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.68 ns 2.68 ns 261249982 +StdFuncCall::noReturn 3.53 ns 3.53 ns 193958493 +ReflectedCall::noReturn 4.54 ns 4.54 ns 154118128 + +StdFuncMethodCall::noReturn 3.70 ns 3.70 ns 189590103 +ReflectedMethodCall::noReturn 7.66 ns 7.66 ns 92278133 +-------------------------------------------------------------------------- +DirectCall::withReturn 7.98 ns 7.98 ns 87518334 +StdFuncCall::withReturn 8.20 ns 8.20 ns 85410105 +ReflectedCall::withReturn 16.5 ns 16.5 ns 42955309 + +StdFuncMethodCall::withReturn 8.19 ns 8.19 ns 85484532 +ReflectedMethodCall::withReturn 18.5 ns 18.5 ns 37798366 +----------------------------------- +>>> Run 3: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:27:17+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4894.06 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.64, 0.47, 0.63 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.68 ns 2.68 ns 262137459 +StdFuncCall::noReturn 3.66 ns 3.66 ns 186118715 +ReflectedCall::noReturn 4.56 ns 4.56 ns 152295433 + +StdFuncMethodCall::noReturn 3.65 ns 3.65 ns 193507678 +ReflectedMethodCall::noReturn 7.68 ns 7.68 ns 89857228 +-------------------------------------------------------------------------- +DirectCall::withReturn 7.99 ns 7.99 ns 87152273 +StdFuncCall::withReturn 8.19 ns 8.19 ns 84706545 +ReflectedCall::withReturn 16.3 ns 16.3 ns 42842896 + +StdFuncMethodCall::withReturn 8.19 ns 8.19 ns 84681044 +ReflectedMethodCall::withReturn 18.5 ns 18.5 ns 38078832 +----------------------------------- +>>> Run 4: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:27:26+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3764.4 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.70, 0.48, 0.64 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.69 ns 2.69 ns 261376697 +StdFuncCall::noReturn 3.71 ns 3.71 ns 190913629 +ReflectedCall::noReturn 4.71 ns 4.71 ns 150141200 + +StdFuncMethodCall::noReturn 3.64 ns 3.64 ns 192162902 +ReflectedMethodCall::noReturn 7.67 ns 7.67 ns 90578497 +-------------------------------------------------------------------------- +DirectCall::withReturn 8.19 ns 8.19 ns 85408568 +StdFuncCall::withReturn 8.39 ns 8.39 ns 83226473 +ReflectedCall::withReturn 16.7 ns 16.7 ns 41844284 + +StdFuncMethodCall::withReturn 8.39 ns 8.39 ns 83073249 +ReflectedMethodCall::withReturn 18.4 ns 18.4 ns 37663093 +----------------------------------- +>>> Run 5: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:27:35+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 1321.93 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.72, 0.49, 0.64 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.69 ns 2.69 ns 259677166 +StdFuncCall::noReturn 3.63 ns 3.63 ns 190282286 +ReflectedCall::noReturn 4.64 ns 4.64 ns 150576640 + +StdFuncMethodCall::noReturn 3.70 ns 3.70 ns 189991616 +ReflectedMethodCall::noReturn 7.59 ns 7.58 ns 88533673 +-------------------------------------------------------------------------- +DirectCall::withReturn 7.98 ns 7.98 ns 86873175 +StdFuncCall::withReturn 8.20 ns 8.20 ns 84535948 +ReflectedCall::withReturn 16.5 ns 16.5 ns 42143122 + +StdFuncMethodCall::withReturn 8.19 ns 8.19 ns 85127244 +ReflectedMethodCall::withReturn 18.7 ns 18.7 ns 37470244 +----------------------------------- +>>> Run 6: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:27:45+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2798.79 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.76, 0.51, 0.64 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.68 ns 2.68 ns 258313912 +StdFuncCall::noReturn 3.45 ns 3.45 ns 202668270 +ReflectedCall::noReturn 4.45 ns 4.45 ns 155726805 + +StdFuncMethodCall::noReturn 3.66 ns 3.66 ns 195257109 +ReflectedMethodCall::noReturn 7.72 ns 7.71 ns 91691488 +-------------------------------------------------------------------------- +DirectCall::withReturn 8.40 ns 8.40 ns 83232030 +StdFuncCall::withReturn 8.19 ns 8.19 ns 84719682 +ReflectedCall::withReturn 16.4 ns 16.4 ns 41763228 + +StdFuncMethodCall::withReturn 8.19 ns 8.19 ns 85406769 +ReflectedMethodCall::withReturn 18.5 ns 18.5 ns 38342729 +----------------------------------- +>>> Run 7: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:27:54+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4820.72 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.80, 0.53, 0.65 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.70 ns 2.70 ns 255687919 +StdFuncCall::noReturn 3.80 ns 3.80 ns 192744917 +ReflectedCall::noReturn 4.54 ns 4.54 ns 155638851 + +StdFuncMethodCall::noReturn 3.71 ns 3.71 ns 189990747 +ReflectedMethodCall::noReturn 7.65 ns 7.65 ns 90475902 +-------------------------------------------------------------------------- +DirectCall::withReturn 8.41 ns 8.41 ns 82783681 +StdFuncCall::withReturn 8.19 ns 8.19 ns 85113727 +ReflectedCall::withReturn 16.4 ns 16.3 ns 43469078 + +StdFuncMethodCall::withReturn 8.21 ns 8.21 ns 85061209 +ReflectedMethodCall::withReturn 18.4 ns 18.4 ns 38318216 +----------------------------------- +>>> Run 8: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:28:04+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.83, 0.54, 0.65 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.67 ns 2.67 ns 259545419 +StdFuncCall::noReturn 3.68 ns 3.68 ns 186620879 +ReflectedCall::noReturn 4.68 ns 4.68 ns 151229098 + +StdFuncMethodCall::noReturn 3.69 ns 3.69 ns 192148650 +ReflectedMethodCall::noReturn 7.65 ns 7.64 ns 92628011 +-------------------------------------------------------------------------- +DirectCall::withReturn 7.98 ns 7.98 ns 87367388 +StdFuncCall::withReturn 8.20 ns 8.20 ns 85464076 +ReflectedCall::withReturn 16.4 ns 16.4 ns 41631547 + +StdFuncMethodCall::withReturn 8.19 ns 8.19 ns 84841078 +ReflectedMethodCall::withReturn 18.5 ns 18.5 ns 38096443 +----------------------------------- +>>> Run 9: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:28:13+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2581.19 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.86, 0.56, 0.66 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.69 ns 2.69 ns 261320876 +StdFuncCall::noReturn 3.76 ns 3.76 ns 183274387 +ReflectedCall::noReturn 4.55 ns 4.55 ns 154257846 + +StdFuncMethodCall::noReturn 3.69 ns 3.69 ns 183824695 +ReflectedMethodCall::noReturn 7.67 ns 7.67 ns 91673266 +-------------------------------------------------------------------------- +DirectCall::withReturn 8.39 ns 8.39 ns 82340975 +StdFuncCall::withReturn 8.19 ns 8.19 ns 85259148 +ReflectedCall::withReturn 16.5 ns 16.5 ns 43168408 + +StdFuncMethodCall::withReturn 8.19 ns 8.19 ns 85150908 +ReflectedMethodCall::withReturn 18.4 ns 18.4 ns 38115440 +----------------------------------- +>>> Run 10: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:28:22+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4020.89 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.88, 0.57, 0.66 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.68 ns 2.68 ns 261232254 +StdFuncCall::noReturn 3.75 ns 3.75 ns 187932171 +ReflectedCall::noReturn 4.51 ns 4.51 ns 152128258 + +StdFuncMethodCall::noReturn 3.65 ns 3.64 ns 190154894 +ReflectedMethodCall::noReturn 7.63 ns 7.63 ns 92473165 +-------------------------------------------------------------------------- +DirectCall::withReturn 8.40 ns 8.40 ns 83290327 +StdFuncCall::withReturn 8.18 ns 8.18 ns 85083846 +ReflectedCall::withReturn 16.7 ns 16.7 ns 41687403 + +StdFuncMethodCall::withReturn 8.20 ns 8.20 ns 85490157 +ReflectedMethodCall::withReturn 18.5 ns 18.5 ns 38928701 +----------------------------------- +>>> Run 1: workload scale = 25 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 25 iterations +============================================= + +2025-09-11T00:28:32+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3453.76 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.90, 0.59, 0.66 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 301 ns 301 ns 2303982 +StdFuncCall::noReturn 300 ns 300 ns 2342931 +ReflectedCall::noReturn 306 ns 306 ns 2284404 + +StdFuncMethodCall::noReturn 301 ns 301 ns 2329281 +ReflectedMethodCall::noReturn 309 ns 309 ns 2277267 +-------------------------------------------------------------------------- +DirectCall::withReturn 373 ns 373 ns 1874653 +StdFuncCall::withReturn 370 ns 370 ns 1870666 +ReflectedCall::withReturn 385 ns 385 ns 1817975 + +StdFuncMethodCall::withReturn 372 ns 372 ns 1861488 +ReflectedMethodCall::withReturn 387 ns 387 ns 1808677 +----------------------------------- +>>> Run 2: workload scale = 25 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 25 iterations +============================================= + +2025-09-11T00:28:42+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 1209.55 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.92, 0.60, 0.67 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 307 ns 307 ns 2302722 +StdFuncCall::noReturn 305 ns 305 ns 2290835 +ReflectedCall::noReturn 303 ns 303 ns 2297945 + +StdFuncMethodCall::noReturn 306 ns 306 ns 2296470 +ReflectedMethodCall::noReturn 305 ns 305 ns 2290296 +-------------------------------------------------------------------------- +DirectCall::withReturn 380 ns 380 ns 1843902 +StdFuncCall::withReturn 430 ns 430 ns 1754632 +ReflectedCall::withReturn 440 ns 440 ns 1589233 + +StdFuncMethodCall::withReturn 431 ns 431 ns 1624807 +ReflectedMethodCall::withReturn 433 ns 433 ns 1611959 +----------------------------------- +>>> Run 3: workload scale = 25 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 25 iterations +============================================= + +2025-09-11T00:28:54+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.93, 0.62, 0.67 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 298 ns 298 ns 2339343 +StdFuncCall::noReturn 298 ns 298 ns 2362369 +ReflectedCall::noReturn 301 ns 301 ns 2308822 + +StdFuncMethodCall::noReturn 297 ns 297 ns 2342636 +ReflectedMethodCall::noReturn 341 ns 341 ns 2303815 +-------------------------------------------------------------------------- +DirectCall::withReturn 425 ns 425 ns 1654379 +StdFuncCall::withReturn 424 ns 424 ns 1650814 +ReflectedCall::withReturn 434 ns 434 ns 1612383 + +StdFuncMethodCall::withReturn 426 ns 426 ns 1639874 +ReflectedMethodCall::withReturn 434 ns 434 ns 1616096 +----------------------------------- +>>> Run 4: workload scale = 25 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 25 iterations +============================================= + +2025-09-11T00:29:05+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 0.94, 0.63, 0.67 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 341 ns 341 ns 2060360 +StdFuncCall::noReturn 334 ns 334 ns 2092530 +ReflectedCall::noReturn 336 ns 336 ns 2084367 + +StdFuncMethodCall::noReturn 335 ns 335 ns 2086575 +ReflectedMethodCall::noReturn 344 ns 344 ns 2032773 +-------------------------------------------------------------------------- +DirectCall::withReturn 421 ns 421 ns 1662603 +StdFuncCall::withReturn 423 ns 423 ns 1666937 +ReflectedCall::withReturn 431 ns 431 ns 1623342 + +StdFuncMethodCall::withReturn 421 ns 421 ns 1658766 +ReflectedMethodCall::withReturn 433 ns 433 ns 1618268 +----------------------------------- +>>> Run 5: workload scale = 25 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 25 iterations +============================================= + +2025-09-11T00:29:16+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4781.24 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.03, 0.66, 0.68 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 339 ns 339 ns 2086251 +StdFuncCall::noReturn 334 ns 334 ns 2096201 +ReflectedCall::noReturn 336 ns 336 ns 2079979 + +StdFuncMethodCall::noReturn 335 ns 335 ns 2092323 +ReflectedMethodCall::noReturn 344 ns 344 ns 2037126 +-------------------------------------------------------------------------- +DirectCall::withReturn 420 ns 420 ns 1666613 +StdFuncCall::withReturn 420 ns 420 ns 1674857 +ReflectedCall::withReturn 431 ns 431 ns 1620878 + +StdFuncMethodCall::withReturn 420 ns 420 ns 1654424 +ReflectedMethodCall::withReturn 432 ns 432 ns 1616853 +----------------------------------- +>>> Run 1: workload scale = 50 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 50 iterations +============================================= + +2025-09-11T00:29:27+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2132.68 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.34, 0.74, 0.71 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 901 ns 901 ns 759524 +StdFuncCall::noReturn 906 ns 906 ns 767684 +ReflectedCall::noReturn 912 ns 912 ns 765256 + +StdFuncMethodCall::noReturn 906 ns 906 ns 769566 +ReflectedMethodCall::noReturn 916 ns 916 ns 762842 +-------------------------------------------------------------------------- +DirectCall::withReturn 1036 ns 1036 ns 675246 +StdFuncCall::withReturn 1035 ns 1034 ns 675401 +ReflectedCall::withReturn 1048 ns 1048 ns 667279 + +StdFuncMethodCall::withReturn 1035 ns 1034 ns 676395 +ReflectedMethodCall::withReturn 1053 ns 1053 ns 665275 +----------------------------------- +>>> Run 2: workload scale = 50 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 50 iterations +============================================= + +2025-09-11T00:29:35+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.32, 0.75, 0.71 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 897 ns 897 ns 759896 +StdFuncCall::noReturn 902 ns 902 ns 776407 +ReflectedCall::noReturn 913 ns 913 ns 769246 + +StdFuncMethodCall::noReturn 902 ns 902 ns 776430 +ReflectedMethodCall::noReturn 919 ns 918 ns 760482 +-------------------------------------------------------------------------- +DirectCall::withReturn 1042 ns 1042 ns 671791 +StdFuncCall::withReturn 1041 ns 1041 ns 671217 +ReflectedCall::withReturn 1051 ns 1051 ns 664465 + +StdFuncMethodCall::withReturn 1041 ns 1041 ns 672055 +ReflectedMethodCall::withReturn 1057 ns 1057 ns 661898 +----------------------------------- +>>> Run 3: workload scale = 50 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 50 iterations +============================================= + +2025-09-11T00:29:43+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 1717.77 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.27, 0.76, 0.72 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 895 ns 894 ns 770126 +StdFuncCall::noReturn 902 ns 902 ns 775366 +ReflectedCall::noReturn 911 ns 911 ns 768376 + +StdFuncMethodCall::noReturn 901 ns 901 ns 772646 +ReflectedMethodCall::noReturn 932 ns 932 ns 748823 +-------------------------------------------------------------------------- +DirectCall::withReturn 1035 ns 1035 ns 675872 +StdFuncCall::withReturn 1034 ns 1034 ns 672118 +ReflectedCall::withReturn 1052 ns 1052 ns 661468 + +StdFuncMethodCall::withReturn 1035 ns 1035 ns 672736 +ReflectedMethodCall::withReturn 1061 ns 1061 ns 659470 +----------------------------------- +>>> Run 4: workload scale = 50 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 50 iterations +============================================= + +2025-09-11T00:29:51+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2364.83 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.23, 0.76, 0.72 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 899 ns 899 ns 757381 +StdFuncCall::noReturn 904 ns 904 ns 769320 +ReflectedCall::noReturn 911 ns 911 ns 762634 + +StdFuncMethodCall::noReturn 902 ns 902 ns 767011 +ReflectedMethodCall::noReturn 954 ns 954 ns 726736 +-------------------------------------------------------------------------- +DirectCall::withReturn 1075 ns 1075 ns 651123 +StdFuncCall::withReturn 1078 ns 1078 ns 648677 +ReflectedCall::withReturn 1087 ns 1087 ns 640363 + +StdFuncMethodCall::withReturn 1076 ns 1076 ns 648598 +ReflectedMethodCall::withReturn 1118 ns 1118 ns 625917 +----------------------------------- +>>> Run 5: workload scale = 50 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 50 iterations +============================================= + +2025-09-11T00:29:59+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.21, 0.77, 0.72 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 896 ns 896 ns 767762 +StdFuncCall::noReturn 910 ns 910 ns 763215 +ReflectedCall::noReturn 923 ns 923 ns 755913 + +StdFuncMethodCall::noReturn 910 ns 909 ns 760598 +ReflectedMethodCall::noReturn 928 ns 928 ns 750732 +-------------------------------------------------------------------------- +DirectCall::withReturn 1080 ns 1080 ns 644211 +StdFuncCall::withReturn 1081 ns 1081 ns 642103 +ReflectedCall::withReturn 1095 ns 1095 ns 638553 + +StdFuncMethodCall::withReturn 1082 ns 1082 ns 642934 +ReflectedMethodCall::withReturn 1098 ns 1098 ns 633816 +----------------------------------- +>>> Run 1: workload scale = 75 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 75 iterations +============================================= + +2025-09-11T00:30:08+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2499.32 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.17, 0.78, 0.73 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1718 ns 1718 ns 403509 +StdFuncCall::noReturn 1711 ns 1711 ns 408919 +ReflectedCall::noReturn 1719 ns 1719 ns 406569 + +StdFuncMethodCall::noReturn 1710 ns 1710 ns 409246 +ReflectedMethodCall::noReturn 1735 ns 1735 ns 403276 +-------------------------------------------------------------------------- +DirectCall::withReturn 1942 ns 1942 ns 360231 +StdFuncCall::withReturn 1944 ns 1944 ns 359937 +ReflectedCall::withReturn 1967 ns 1967 ns 355508 + +StdFuncMethodCall::withReturn 1945 ns 1945 ns 360153 +ReflectedMethodCall::withReturn 1971 ns 1971 ns 354944 +----------------------------------- +>>> Run 2: workload scale = 75 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 75 iterations +============================================= + +2025-09-11T00:30:17+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2418.55 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.15, 0.78, 0.73 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1720 ns 1719 ns 403240 +StdFuncCall::noReturn 1711 ns 1711 ns 409539 +ReflectedCall::noReturn 1722 ns 1722 ns 407137 + +StdFuncMethodCall::noReturn 1710 ns 1710 ns 408876 +ReflectedMethodCall::noReturn 1735 ns 1735 ns 403563 +-------------------------------------------------------------------------- +DirectCall::withReturn 1905 ns 1905 ns 364610 +StdFuncCall::withReturn 1906 ns 1905 ns 367144 +ReflectedCall::withReturn 1932 ns 1931 ns 362876 + +StdFuncMethodCall::withReturn 1904 ns 1904 ns 367010 +ReflectedMethodCall::withReturn 1941 ns 1940 ns 360733 +----------------------------------- +>>> Run 3: workload scale = 75 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 75 iterations +============================================= + +2025-09-11T00:30:26+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4754.64 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.14, 0.79, 0.73 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1700 ns 1700 ns 408515 +StdFuncCall::noReturn 1706 ns 1706 ns 411515 +ReflectedCall::noReturn 1711 ns 1710 ns 409353 + +StdFuncMethodCall::noReturn 1702 ns 1702 ns 410901 +ReflectedMethodCall::noReturn 1723 ns 1722 ns 406169 +-------------------------------------------------------------------------- +DirectCall::withReturn 1888 ns 1888 ns 370526 +StdFuncCall::withReturn 1888 ns 1887 ns 370612 +ReflectedCall::withReturn 1916 ns 1916 ns 365093 + +StdFuncMethodCall::withReturn 1890 ns 1889 ns 371318 +ReflectedMethodCall::withReturn 1927 ns 1927 ns 362766 +----------------------------------- +>>> Run 4: workload scale = 75 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 75 iterations +============================================= + +2025-09-11T00:30:35+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 1859.33 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.11, 0.80, 0.73 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1708 ns 1707 ns 404396 +StdFuncCall::noReturn 1709 ns 1708 ns 409461 +ReflectedCall::noReturn 1717 ns 1717 ns 407033 + +StdFuncMethodCall::noReturn 1708 ns 1708 ns 409492 +ReflectedMethodCall::noReturn 1732 ns 1731 ns 404102 +-------------------------------------------------------------------------- +DirectCall::withReturn 1949 ns 1948 ns 359921 +StdFuncCall::withReturn 1944 ns 1944 ns 360031 +ReflectedCall::withReturn 1969 ns 1968 ns 355377 + +StdFuncMethodCall::withReturn 2043 ns 2042 ns 358913 +ReflectedMethodCall::withReturn 2102 ns 2102 ns 351247 +----------------------------------- +>>> Run 5: workload scale = 75 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 75 iterations +============================================= + +2025-09-11T00:30:44+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4274.37 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.10, 0.80, 0.74 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1854 ns 1853 ns 369519 +StdFuncCall::noReturn 1804 ns 1804 ns 389051 +ReflectedCall::noReturn 1768 ns 1767 ns 372147 + +StdFuncMethodCall::noReturn 1845 ns 1845 ns 366941 +ReflectedMethodCall::noReturn 1892 ns 1891 ns 390576 +-------------------------------------------------------------------------- +DirectCall::withReturn 2059 ns 2059 ns 321965 +StdFuncCall::withReturn 2102 ns 2102 ns 331998 +ReflectedCall::withReturn 2122 ns 2122 ns 333219 + +StdFuncMethodCall::withReturn 2060 ns 2060 ns 333788 +ReflectedMethodCall::withReturn 2079 ns 2079 ns 331928 +----------------------------------- +>>> Run 1: workload scale = 100 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 100 iterations +============================================= + +2025-09-11T00:30:53+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.24, 0.84, 0.75 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1960 ns 1959 ns 357975 +StdFuncCall::noReturn 2034 ns 2034 ns 341249 +ReflectedCall::noReturn 2022 ns 2022 ns 341827 + +StdFuncMethodCall::noReturn 2013 ns 2013 ns 303915 +ReflectedMethodCall::noReturn 2014 ns 2014 ns 343680 +-------------------------------------------------------------------------- +DirectCall::withReturn 2207 ns 2207 ns 310242 +StdFuncCall::withReturn 2217 ns 2216 ns 310202 +ReflectedCall::withReturn 2254 ns 2253 ns 304225 + +StdFuncMethodCall::withReturn 2240 ns 2239 ns 306452 +ReflectedMethodCall::withReturn 2265 ns 2264 ns 299926 +----------------------------------- +>>> Run 2: workload scale = 100 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 100 iterations +============================================= + +2025-09-11T00:31:02+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3021.9 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.43, 0.90, 0.77 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1981 ns 1981 ns 342744 +StdFuncCall::noReturn 1966 ns 1965 ns 352075 +ReflectedCall::noReturn 2002 ns 2001 ns 345397 + +StdFuncMethodCall::noReturn 2081 ns 2080 ns 341551 +ReflectedMethodCall::noReturn 1969 ns 1969 ns 333753 +-------------------------------------------------------------------------- +DirectCall::withReturn 2207 ns 2207 ns 316294 +StdFuncCall::withReturn 2208 ns 2207 ns 308338 +ReflectedCall::withReturn 2215 ns 2215 ns 312972 + +StdFuncMethodCall::withReturn 2192 ns 2191 ns 314740 +ReflectedMethodCall::withReturn 2224 ns 2223 ns 305823 +----------------------------------- +>>> Run 3: workload scale = 100 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 100 iterations +============================================= + +2025-09-11T00:31:11+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4705.12 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.44, 0.92, 0.78 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1940 ns 1939 ns 350727 +StdFuncCall::noReturn 1965 ns 1965 ns 347915 +ReflectedCall::noReturn 1951 ns 1951 ns 352534 + +StdFuncMethodCall::noReturn 1938 ns 1938 ns 359757 +ReflectedMethodCall::noReturn 1965 ns 1964 ns 356003 +-------------------------------------------------------------------------- +DirectCall::withReturn 2195 ns 2194 ns 317298 +StdFuncCall::withReturn 2197 ns 2196 ns 316093 +ReflectedCall::withReturn 2214 ns 2213 ns 302642 + +StdFuncMethodCall::withReturn 2197 ns 2197 ns 318178 +ReflectedMethodCall::withReturn 2227 ns 2226 ns 313484 +----------------------------------- +>>> Run 4: workload scale = 100 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 100 iterations +============================================= + +2025-09-11T00:31:21+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2658.72 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.48, 0.94, 0.79 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2027 ns 2027 ns 330486 +StdFuncCall::noReturn 2014 ns 2013 ns 352994 +ReflectedCall::noReturn 2031 ns 2030 ns 345623 + +StdFuncMethodCall::noReturn 1982 ns 1982 ns 341573 +ReflectedMethodCall::noReturn 2017 ns 2017 ns 349908 +-------------------------------------------------------------------------- +DirectCall::withReturn 2296 ns 2296 ns 294346 +StdFuncCall::withReturn 2321 ns 2319 ns 300846 +ReflectedCall::withReturn 2360 ns 2360 ns 286766 + +StdFuncMethodCall::withReturn 2270 ns 2270 ns 301661 +ReflectedMethodCall::withReturn 2284 ns 2284 ns 291059 +----------------------------------- +>>> Run 5: workload scale = 100 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 100 iterations +============================================= + +2025-09-11T00:31:30+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2259.74 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.71, 1.00, 0.81 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1977 ns 1977 ns 353401 +StdFuncCall::noReturn 2000 ns 2000 ns 345838 +ReflectedCall::noReturn 1986 ns 1985 ns 351401 + +StdFuncMethodCall::noReturn 1963 ns 1962 ns 353888 +ReflectedMethodCall::noReturn 1965 ns 1965 ns 348781 +-------------------------------------------------------------------------- +DirectCall::withReturn 2208 ns 2207 ns 312994 +StdFuncCall::withReturn 2208 ns 2208 ns 312348 +ReflectedCall::withReturn 2234 ns 2234 ns 311258 + +StdFuncMethodCall::withReturn 2210 ns 2209 ns 313612 +ReflectedMethodCall::withReturn 2243 ns 2243 ns 310464 +----------------------------------- +>>> Run 1: workload scale = 125 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 125 iterations +============================================= + +2025-09-11T00:31:39+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2565.99 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.89, 1.07, 0.83 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2186 ns 2186 ns 298930 +StdFuncCall::noReturn 2180 ns 2179 ns 316013 +ReflectedCall::noReturn 2185 ns 2185 ns 317280 + +StdFuncMethodCall::noReturn 2178 ns 2178 ns 318283 +ReflectedMethodCall::noReturn 2199 ns 2199 ns 317096 +-------------------------------------------------------------------------- +DirectCall::withReturn 2651 ns 2651 ns 262036 +StdFuncCall::withReturn 2659 ns 2659 ns 262524 +ReflectedCall::withReturn 2687 ns 2686 ns 259459 + +StdFuncMethodCall::withReturn 2662 ns 2661 ns 260168 +ReflectedMethodCall::withReturn 2689 ns 2689 ns 255403 +----------------------------------- +>>> Run 2: workload scale = 125 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 125 iterations +============================================= + +2025-09-11T00:31:49+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.76, 1.07, 0.84 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2167 ns 2166 ns 317573 +StdFuncCall::noReturn 2178 ns 2178 ns 319987 +ReflectedCall::noReturn 2173 ns 2172 ns 319725 + +StdFuncMethodCall::noReturn 2167 ns 2167 ns 319864 +ReflectedMethodCall::noReturn 2215 ns 2215 ns 314983 +-------------------------------------------------------------------------- +DirectCall::withReturn 2597 ns 2596 ns 267537 +StdFuncCall::withReturn 2609 ns 2608 ns 263587 +ReflectedCall::withReturn 2639 ns 2638 ns 264562 + +StdFuncMethodCall::withReturn 2594 ns 2593 ns 268609 +ReflectedMethodCall::withReturn 2649 ns 2648 ns 261252 +----------------------------------- +>>> Run 3: workload scale = 125 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 125 iterations +============================================= + +2025-09-11T00:31:59+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.64, 1.06, 0.84 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2187 ns 2187 ns 311240 +StdFuncCall::noReturn 2195 ns 2195 ns 321853 +ReflectedCall::noReturn 2195 ns 2194 ns 315480 + +StdFuncMethodCall::noReturn 2188 ns 2187 ns 319537 +ReflectedMethodCall::noReturn 2212 ns 2212 ns 315561 +-------------------------------------------------------------------------- +DirectCall::withReturn 2636 ns 2635 ns 264251 +StdFuncCall::withReturn 2625 ns 2624 ns 264662 +ReflectedCall::withReturn 2654 ns 2653 ns 261530 + +StdFuncMethodCall::withReturn 2625 ns 2625 ns 262978 +ReflectedMethodCall::withReturn 2668 ns 2668 ns 259997 +----------------------------------- +>>> Run 4: workload scale = 125 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 125 iterations +============================================= + +2025-09-11T00:32:08+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.54, 1.06, 0.84 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2181 ns 2180 ns 317614 +StdFuncCall::noReturn 2172 ns 2172 ns 317161 +ReflectedCall::noReturn 2190 ns 2190 ns 321018 + +StdFuncMethodCall::noReturn 2281 ns 2281 ns 304077 +ReflectedMethodCall::noReturn 2242 ns 2242 ns 290170 +-------------------------------------------------------------------------- +DirectCall::withReturn 2601 ns 2601 ns 266644 +StdFuncCall::withReturn 2723 ns 2723 ns 263459 +ReflectedCall::withReturn 2724 ns 2723 ns 257965 + +StdFuncMethodCall::withReturn 2611 ns 2611 ns 262426 +ReflectedMethodCall::withReturn 2683 ns 2682 ns 258950 +----------------------------------- +>>> Run 5: workload scale = 125 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 125 iterations +============================================= + +2025-09-11T00:32:18+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.46, 1.06, 0.84 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2195 ns 2195 ns 313426 +StdFuncCall::noReturn 2183 ns 2182 ns 312811 +ReflectedCall::noReturn 2182 ns 2181 ns 320013 + +StdFuncMethodCall::noReturn 2184 ns 2184 ns 318661 +ReflectedMethodCall::noReturn 2194 ns 2194 ns 317609 +-------------------------------------------------------------------------- +DirectCall::withReturn 2601 ns 2601 ns 261526 +StdFuncCall::withReturn 2598 ns 2597 ns 266007 +ReflectedCall::withReturn 2623 ns 2623 ns 264649 + +StdFuncMethodCall::withReturn 2593 ns 2593 ns 268179 +ReflectedMethodCall::withReturn 2643 ns 2642 ns 261745 +----------------------------------- +>>> Run 1: workload scale = 150 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 150 iterations +============================================= + +2025-09-11T00:32:28+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.46, 1.07, 0.85 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3533 ns 3532 ns 195801 +StdFuncCall::noReturn 3526 ns 3525 ns 197420 +ReflectedCall::noReturn 3535 ns 3535 ns 196633 + +StdFuncMethodCall::noReturn 3524 ns 3523 ns 197148 +ReflectedMethodCall::noReturn 3546 ns 3546 ns 194787 +-------------------------------------------------------------------------- +DirectCall::withReturn 3992 ns 3992 ns 173680 +StdFuncCall::withReturn 3995 ns 3994 ns 174034 +ReflectedCall::withReturn 4028 ns 4028 ns 172386 + +StdFuncMethodCall::withReturn 3989 ns 3989 ns 173898 +ReflectedMethodCall::withReturn 4041 ns 4040 ns 172324 +----------------------------------- +>>> Run 2: workload scale = 150 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 150 iterations +============================================= + +2025-09-11T00:32:39+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3872.32 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.47, 1.09, 0.86 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3520 ns 3520 ns 194024 +StdFuncCall::noReturn 3532 ns 3532 ns 196774 +ReflectedCall::noReturn 3531 ns 3530 ns 196912 + +StdFuncMethodCall::noReturn 3527 ns 3526 ns 197173 +ReflectedMethodCall::noReturn 3545 ns 3544 ns 195935 +-------------------------------------------------------------------------- +DirectCall::withReturn 3988 ns 3987 ns 174580 +StdFuncCall::withReturn 3986 ns 3985 ns 175002 +ReflectedCall::withReturn 4015 ns 4014 ns 172705 + +StdFuncMethodCall::withReturn 4088 ns 4087 ns 174540 +ReflectedMethodCall::withReturn 4095 ns 4094 ns 169848 +----------------------------------- +>>> Run 3: workload scale = 150 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 150 iterations +============================================= + +2025-09-11T00:32:50+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.40, 1.08, 0.86 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3552 ns 3552 ns 195236 +StdFuncCall::noReturn 3563 ns 3562 ns 195128 +ReflectedCall::noReturn 3559 ns 3558 ns 195504 + +StdFuncMethodCall::noReturn 3559 ns 3559 ns 195249 +ReflectedMethodCall::noReturn 3574 ns 3573 ns 195050 +-------------------------------------------------------------------------- +DirectCall::withReturn 4036 ns 4035 ns 173264 +StdFuncCall::withReturn 4022 ns 4021 ns 171261 +ReflectedCall::withReturn 4060 ns 4059 ns 171454 + +StdFuncMethodCall::withReturn 4025 ns 4024 ns 172789 +ReflectedMethodCall::withReturn 4065 ns 4064 ns 171108 +----------------------------------- +>>> Run 4: workload scale = 150 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 150 iterations +============================================= + +2025-09-11T00:33:01+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.41, 1.10, 0.87 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3500 ns 3500 ns 197777 +StdFuncCall::noReturn 3519 ns 3518 ns 198267 +ReflectedCall::noReturn 3539 ns 3538 ns 197240 + +StdFuncMethodCall::noReturn 3539 ns 3538 ns 197720 +ReflectedMethodCall::noReturn 3563 ns 3563 ns 196002 +-------------------------------------------------------------------------- +DirectCall::withReturn 4079 ns 4078 ns 171166 +StdFuncCall::withReturn 4069 ns 4069 ns 171810 +ReflectedCall::withReturn 4099 ns 4097 ns 170406 + +StdFuncMethodCall::withReturn 4063 ns 4061 ns 170961 +ReflectedMethodCall::withReturn 4036 ns 4035 ns 172161 +----------------------------------- +>>> Run 5: workload scale = 150 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 150 iterations +============================================= + +2025-09-11T00:33:12+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 1801.89 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.39, 1.11, 0.87 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3537 ns 3536 ns 198280 +StdFuncCall::noReturn 3515 ns 3515 ns 198830 +ReflectedCall::noReturn 3521 ns 3520 ns 198314 + +StdFuncMethodCall::noReturn 3514 ns 3514 ns 196875 +ReflectedMethodCall::noReturn 3586 ns 3586 ns 194396 +-------------------------------------------------------------------------- +DirectCall::withReturn 4039 ns 4038 ns 173252 +StdFuncCall::withReturn 4029 ns 4029 ns 174445 +ReflectedCall::withReturn 4015 ns 4014 ns 172859 + +StdFuncMethodCall::withReturn 3996 ns 3996 ns 174457 +ReflectedMethodCall::withReturn 4030 ns 4029 ns 172232 +----------------------------------- +>>> Run 1: workload scale = 175 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 175 iterations +============================================= + +2025-09-11T00:33:24+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4390.34 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.33, 1.10, 0.88 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3802 ns 3801 ns 184513 +StdFuncCall::noReturn 3795 ns 3794 ns 184144 +ReflectedCall::noReturn 3790 ns 3789 ns 184898 + +StdFuncMethodCall::noReturn 3765 ns 3764 ns 177760 +ReflectedMethodCall::noReturn 3778 ns 3778 ns 183638 +-------------------------------------------------------------------------- +DirectCall::withReturn 4329 ns 4328 ns 161157 +StdFuncCall::withReturn 4332 ns 4330 ns 160003 +ReflectedCall::withReturn 4367 ns 4366 ns 159612 + +StdFuncMethodCall::withReturn 4335 ns 4335 ns 160361 +ReflectedMethodCall::withReturn 4372 ns 4371 ns 159538 +----------------------------------- +>>> Run 2: workload scale = 175 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 175 iterations +============================================= + +2025-09-11T00:33:35+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4300.1 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.28, 1.10, 0.88 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3778 ns 3777 ns 183159 +StdFuncCall::noReturn 3762 ns 3761 ns 185032 +ReflectedCall::noReturn 3764 ns 3762 ns 185710 + +StdFuncMethodCall::noReturn 3758 ns 3757 ns 185331 +ReflectedMethodCall::noReturn 3779 ns 3778 ns 176155 +-------------------------------------------------------------------------- +DirectCall::withReturn 4339 ns 4338 ns 159051 +StdFuncCall::withReturn 4342 ns 4342 ns 160567 +ReflectedCall::withReturn 4363 ns 4362 ns 159875 + +StdFuncMethodCall::withReturn 4338 ns 4337 ns 160163 +ReflectedMethodCall::withReturn 4382 ns 4382 ns 159491 +----------------------------------- +>>> Run 3: workload scale = 175 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 175 iterations +============================================= + +2025-09-11T00:33:47+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.21, 1.09, 0.88 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3768 ns 3767 ns 186032 +StdFuncCall::noReturn 3753 ns 3753 ns 181678 +ReflectedCall::noReturn 3767 ns 3767 ns 184272 + +StdFuncMethodCall::noReturn 3759 ns 3758 ns 183679 +ReflectedMethodCall::noReturn 3774 ns 3773 ns 184695 +-------------------------------------------------------------------------- +DirectCall::withReturn 4341 ns 4341 ns 160622 +StdFuncCall::withReturn 4356 ns 4355 ns 159689 +ReflectedCall::withReturn 4369 ns 4368 ns 159362 + +StdFuncMethodCall::withReturn 4353 ns 4352 ns 160835 +ReflectedMethodCall::withReturn 4376 ns 4375 ns 159727 +----------------------------------- +>>> Run 4: workload scale = 175 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 175 iterations +============================================= + +2025-09-11T00:33:58+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.26, 1.11, 0.89 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3755 ns 3754 ns 186615 +StdFuncCall::noReturn 3745 ns 3744 ns 184616 +ReflectedCall::noReturn 3771 ns 3771 ns 185449 + +StdFuncMethodCall::noReturn 3747 ns 3747 ns 186113 +ReflectedMethodCall::noReturn 3773 ns 3772 ns 182904 +-------------------------------------------------------------------------- +DirectCall::withReturn 4325 ns 4323 ns 161325 +StdFuncCall::withReturn 4321 ns 4321 ns 160422 +ReflectedCall::withReturn 4348 ns 4347 ns 159032 + +StdFuncMethodCall::withReturn 4315 ns 4315 ns 159476 +ReflectedMethodCall::withReturn 4363 ns 4362 ns 159343 +----------------------------------- +>>> Run 5: workload scale = 175 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 175 iterations +============================================= + +2025-09-11T00:34:10+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.22, 1.10, 0.89 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3824 ns 3823 ns 182597 +StdFuncCall::noReturn 3822 ns 3822 ns 182367 +ReflectedCall::noReturn 3835 ns 3835 ns 182224 + +StdFuncMethodCall::noReturn 3820 ns 3820 ns 182550 +ReflectedMethodCall::noReturn 3878 ns 3877 ns 179398 +-------------------------------------------------------------------------- +DirectCall::withReturn 4498 ns 4497 ns 153854 +StdFuncCall::withReturn 4437 ns 4436 ns 157785 +ReflectedCall::withReturn 4484 ns 4483 ns 156149 + +StdFuncMethodCall::withReturn 4469 ns 4467 ns 155734 +ReflectedMethodCall::withReturn 4484 ns 4483 ns 154092 +----------------------------------- +>>> Run 1: workload scale = 200 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 200 iterations +============================================= + +2025-09-11T00:34:21+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2714.66 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.19, 1.10, 0.89 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 4158 ns 4157 ns 168841 +StdFuncCall::noReturn 4107 ns 4107 ns 167421 +ReflectedCall::noReturn 4073 ns 4072 ns 172223 + +StdFuncMethodCall::noReturn 4072 ns 4071 ns 171726 +ReflectedMethodCall::noReturn 4078 ns 4077 ns 171379 +-------------------------------------------------------------------------- +DirectCall::withReturn 4718 ns 4718 ns 148098 +StdFuncCall::withReturn 4703 ns 4702 ns 146865 +ReflectedCall::withReturn 4735 ns 4733 ns 146683 + +StdFuncMethodCall::withReturn 4858 ns 4858 ns 147228 +ReflectedMethodCall::withReturn 4877 ns 4876 ns 135299 +----------------------------------- +>>> Run 2: workload scale = 200 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 200 iterations +============================================= + +2025-09-11T00:34:33+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4000.23 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.14, 1.09, 0.90 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 4149 ns 4149 ns 171540 +StdFuncCall::noReturn 4078 ns 4078 ns 173142 +ReflectedCall::noReturn 4163 ns 4162 ns 172296 + +StdFuncMethodCall::noReturn 4164 ns 4163 ns 171601 +ReflectedMethodCall::noReturn 4206 ns 4206 ns 167024 +-------------------------------------------------------------------------- +DirectCall::withReturn 4803 ns 4802 ns 146401 +StdFuncCall::withReturn 4767 ns 4766 ns 146265 +ReflectedCall::withReturn 4813 ns 4813 ns 144824 + +StdFuncMethodCall::withReturn 4797 ns 4797 ns 144938 +ReflectedMethodCall::withReturn 4842 ns 4841 ns 143714 +----------------------------------- +>>> Run 3: workload scale = 200 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 200 iterations +============================================= + +2025-09-11T00:34:45+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3428.64 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.20, 1.11, 0.90 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 4051 ns 4050 ns 169785 +StdFuncCall::noReturn 4060 ns 4060 ns 171288 +ReflectedCall::noReturn 4069 ns 4067 ns 171269 + +StdFuncMethodCall::noReturn 4037 ns 4036 ns 171130 +ReflectedMethodCall::noReturn 4070 ns 4069 ns 170385 +-------------------------------------------------------------------------- +DirectCall::withReturn 4715 ns 4714 ns 147560 +StdFuncCall::withReturn 4710 ns 4710 ns 147903 +ReflectedCall::withReturn 4732 ns 4730 ns 146870 + +StdFuncMethodCall::withReturn 4706 ns 4705 ns 147200 +ReflectedMethodCall::withReturn 4743 ns 4741 ns 146280 +----------------------------------- +>>> Run 4: workload scale = 200 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 200 iterations +============================================= + +2025-09-11T00:34:56+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3097.37 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.15, 1.10, 0.91 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 4049 ns 4048 ns 172698 +StdFuncCall::noReturn 4028 ns 4028 ns 172826 +ReflectedCall::noReturn 4020 ns 4019 ns 172895 + +StdFuncMethodCall::noReturn 4011 ns 4010 ns 173064 +ReflectedMethodCall::noReturn 4022 ns 4022 ns 167520 +-------------------------------------------------------------------------- +DirectCall::withReturn 4674 ns 4673 ns 149145 +StdFuncCall::withReturn 4664 ns 4663 ns 148506 +ReflectedCall::withReturn 4701 ns 4700 ns 148023 + +StdFuncMethodCall::withReturn 4671 ns 4670 ns 149320 +ReflectedMethodCall::withReturn 4706 ns 4706 ns 148082 +----------------------------------- +>>> Run 5: workload scale = 200 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 200 iterations +============================================= + +2025-09-11T00:35:08+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 1963.71 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.13, 1.10, 0.91 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 4035 ns 4035 ns 172990 +StdFuncCall::noReturn 4036 ns 4035 ns 169934 +ReflectedCall::noReturn 4037 ns 4035 ns 173552 + +StdFuncMethodCall::noReturn 4027 ns 4027 ns 173475 +ReflectedMethodCall::noReturn 4046 ns 4046 ns 172800 +-------------------------------------------------------------------------- +DirectCall::withReturn 4683 ns 4682 ns 149053 +StdFuncCall::withReturn 4664 ns 4663 ns 148728 +ReflectedCall::withReturn 4695 ns 4694 ns 148659 + +StdFuncMethodCall::withReturn 4666 ns 4665 ns 148374 +ReflectedMethodCall::withReturn 4716 ns 4715 ns 147755 +----------------------------------- +All benchmarks completed. diff --git a/text-benchmark-logs/benchmark_returns_std_string.log b/text-benchmark-logs/benchmark_returns_std_string.log new file mode 100644 index 00000000..1a714ce3 --- /dev/null +++ b/text-benchmark-logs/benchmark_returns_std_string.log @@ -0,0 +1,1655 @@ +Starting benchmark runs... +Binary: ./bin/RTLBenchmarkApp +Log: ./benchmark_runs.log +=================================== +>>> Run 1: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:43:11+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4157.05 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 2.65, 1.34, 1.02 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.69 ns 2.69 ns 257512106 +StdFuncCall::noReturn 3.78 ns 3.78 ns 191372055 +ReflectedCall::noReturn 4.75 ns 4.74 ns 150632849 + +StdFuncMethodCall::noReturn 3.37 ns 3.37 ns 205734218 +ReflectedMethodCall::noReturn 8.16 ns 8.15 ns 83185450 +-------------------------------------------------------------------------- +DirectCall::withReturn 9.53 ns 9.52 ns 73618012 +StdFuncCall::withReturn 10.1 ns 10.1 ns 69035901 +ReflectedCall::withReturn 18.9 ns 18.9 ns 36880785 + +StdFuncMethodCall::withReturn 10.1 ns 10.1 ns 69220259 +ReflectedMethodCall::withReturn 22.1 ns 22.1 ns 31879199 +----------------------------------- +>>> Run 2: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:43:21+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2840.42 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 2.40, 1.33, 1.02 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.68 ns 2.68 ns 260488715 +StdFuncCall::noReturn 3.62 ns 3.62 ns 200782884 +ReflectedCall::noReturn 4.56 ns 4.56 ns 148944703 + +StdFuncMethodCall::noReturn 3.39 ns 3.39 ns 208809945 +ReflectedMethodCall::noReturn 8.11 ns 8.10 ns 87050327 +-------------------------------------------------------------------------- +DirectCall::withReturn 9.60 ns 9.60 ns 72996970 +StdFuncCall::withReturn 10.1 ns 10.1 ns 65628479 +ReflectedCall::withReturn 20.2 ns 20.2 ns 35013149 + +StdFuncMethodCall::withReturn 10.4 ns 10.4 ns 65866447 +ReflectedMethodCall::withReturn 23.0 ns 23.0 ns 30671071 +----------------------------------- +>>> Run 3: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:43:30+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4460.24 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 2.18, 1.32, 1.02 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.87 ns 2.87 ns 241103915 +StdFuncCall::noReturn 3.82 ns 3.82 ns 196511816 +ReflectedCall::noReturn 4.81 ns 4.81 ns 146383768 + +StdFuncMethodCall::noReturn 3.44 ns 3.44 ns 203121679 +ReflectedMethodCall::noReturn 8.30 ns 8.29 ns 81544882 +-------------------------------------------------------------------------- +DirectCall::withReturn 9.71 ns 9.70 ns 70716392 +StdFuncCall::withReturn 10.2 ns 10.2 ns 68308824 +ReflectedCall::withReturn 19.1 ns 19.1 ns 35574641 + +StdFuncMethodCall::withReturn 10.4 ns 10.4 ns 67503571 +ReflectedMethodCall::withReturn 22.1 ns 22.1 ns 31856370 +----------------------------------- +>>> Run 4: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:43:40+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3303.29 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 2.15, 1.34, 1.03 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.78 ns 2.78 ns 244476044 +StdFuncCall::noReturn 3.51 ns 3.51 ns 186742133 +ReflectedCall::noReturn 4.72 ns 4.72 ns 145124302 + +StdFuncMethodCall::noReturn 3.71 ns 3.71 ns 195045272 +ReflectedMethodCall::noReturn 8.33 ns 8.32 ns 83825689 +-------------------------------------------------------------------------- +DirectCall::withReturn 9.94 ns 9.94 ns 67349049 +StdFuncCall::withReturn 10.1 ns 10.1 ns 66482926 +ReflectedCall::withReturn 19.2 ns 19.2 ns 36102397 + +StdFuncMethodCall::withReturn 10.1 ns 10.1 ns 67142276 +ReflectedMethodCall::withReturn 22.8 ns 22.8 ns 29914325 +----------------------------------- +>>> Run 5: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:43:49+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 1457.53 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 2.21, 1.38, 1.05 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.72 ns 2.72 ns 252082597 +StdFuncCall::noReturn 3.57 ns 3.57 ns 187629241 +ReflectedCall::noReturn 4.85 ns 4.85 ns 145919895 + +StdFuncMethodCall::noReturn 3.47 ns 3.47 ns 205701290 +ReflectedMethodCall::noReturn 8.19 ns 8.19 ns 82512348 +-------------------------------------------------------------------------- +DirectCall::withReturn 9.82 ns 9.82 ns 68734745 +StdFuncCall::withReturn 10.2 ns 10.2 ns 66343231 +ReflectedCall::withReturn 19.1 ns 19.1 ns 36036806 + +StdFuncMethodCall::withReturn 10.1 ns 10.1 ns 66426936 +ReflectedMethodCall::withReturn 21.9 ns 21.9 ns 30764432 +----------------------------------- +>>> Run 6: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:43:59+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4134.64 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 2.10, 1.39, 1.05 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.68 ns 2.68 ns 255151415 +StdFuncCall::noReturn 3.57 ns 3.57 ns 194877796 +ReflectedCall::noReturn 4.68 ns 4.68 ns 148012652 + +StdFuncMethodCall::noReturn 3.41 ns 3.41 ns 203119855 +ReflectedMethodCall::noReturn 8.27 ns 8.27 ns 84231855 +-------------------------------------------------------------------------- +DirectCall::withReturn 9.91 ns 9.91 ns 67237407 +StdFuncCall::withReturn 10.1 ns 10.1 ns 66701430 +ReflectedCall::withReturn 19.0 ns 19.0 ns 35306832 + +StdFuncMethodCall::withReturn 10.1 ns 10.1 ns 66190294 +ReflectedMethodCall::withReturn 22.8 ns 22.8 ns 31433430 +----------------------------------- +>>> Run 7: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:44:08+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3065.25 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 2.01, 1.39, 1.06 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.76 ns 2.75 ns 258152508 +StdFuncCall::noReturn 3.54 ns 3.54 ns 188663322 +ReflectedCall::noReturn 4.70 ns 4.70 ns 148190073 + +StdFuncMethodCall::noReturn 3.48 ns 3.48 ns 204262478 +ReflectedMethodCall::noReturn 8.15 ns 8.15 ns 82688841 +-------------------------------------------------------------------------- +DirectCall::withReturn 9.71 ns 9.71 ns 70719451 +StdFuncCall::withReturn 10.2 ns 10.2 ns 66710422 +ReflectedCall::withReturn 19.2 ns 19.2 ns 35438621 + +StdFuncMethodCall::withReturn 10.1 ns 10.1 ns 64913832 +ReflectedMethodCall::withReturn 22.6 ns 22.5 ns 30465366 +----------------------------------- +>>> Run 8: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:44:17+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4152.63 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.85, 1.38, 1.06 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.70 ns 2.70 ns 258092134 +StdFuncCall::noReturn 3.59 ns 3.59 ns 190138329 +ReflectedCall::noReturn 4.77 ns 4.77 ns 148249878 + +StdFuncMethodCall::noReturn 3.49 ns 3.49 ns 201288659 +ReflectedMethodCall::noReturn 8.16 ns 8.16 ns 81918693 +-------------------------------------------------------------------------- +DirectCall::withReturn 9.91 ns 9.91 ns 70546801 +StdFuncCall::withReturn 10.2 ns 10.2 ns 65288578 +ReflectedCall::withReturn 18.9 ns 18.9 ns 35386904 + +StdFuncMethodCall::withReturn 10.2 ns 10.2 ns 66910973 +ReflectedMethodCall::withReturn 22.8 ns 22.8 ns 30535267 +----------------------------------- +>>> Run 9: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:44:27+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4236.18 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.88, 1.40, 1.07 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2.75 ns 2.75 ns 256627078 +StdFuncCall::noReturn 3.62 ns 3.62 ns 191604367 +ReflectedCall::noReturn 4.90 ns 4.90 ns 146177763 + +StdFuncMethodCall::noReturn 3.51 ns 3.51 ns 207243986 +ReflectedMethodCall::noReturn 8.34 ns 8.34 ns 85440083 +-------------------------------------------------------------------------- +DirectCall::withReturn 9.71 ns 9.71 ns 70073783 +StdFuncCall::withReturn 10.3 ns 10.3 ns 64837520 +ReflectedCall::withReturn 19.2 ns 19.2 ns 36494857 + +StdFuncMethodCall::withReturn 10.2 ns 10.2 ns 64693494 +ReflectedMethodCall::withReturn 22.5 ns 22.5 ns 31231450 +----------------------------------- +>>> Run 10: workload scale = 0 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 0 iterations +============================================= + +2025-09-11T00:44:36+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3410.16 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.80, 1.39, 1.07 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3.18 ns 3.18 ns 220289404 +StdFuncCall::noReturn 3.68 ns 3.68 ns 185037995 +ReflectedCall::noReturn 4.95 ns 4.95 ns 138642595 + +StdFuncMethodCall::noReturn 3.69 ns 3.69 ns 191910140 +ReflectedMethodCall::noReturn 8.40 ns 8.40 ns 82408566 +-------------------------------------------------------------------------- +DirectCall::withReturn 9.78 ns 9.78 ns 68512931 +StdFuncCall::withReturn 10.1 ns 10.1 ns 64820748 +ReflectedCall::withReturn 19.0 ns 19.0 ns 36083173 + +StdFuncMethodCall::withReturn 10.2 ns 10.2 ns 66179461 +ReflectedMethodCall::withReturn 22.4 ns 22.4 ns 31255435 +----------------------------------- +>>> Run 1: workload scale = 25 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 25 iterations +============================================= + +2025-09-11T00:44:45+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3869.61 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.76, 1.39, 1.07 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 310 ns 310 ns 2254101 +StdFuncCall::noReturn 315 ns 315 ns 2239763 +ReflectedCall::noReturn 316 ns 316 ns 2198780 + +StdFuncMethodCall::noReturn 313 ns 313 ns 2258100 +ReflectedMethodCall::noReturn 323 ns 323 ns 2166074 +-------------------------------------------------------------------------- +DirectCall::withReturn 453 ns 453 ns 1577637 +StdFuncCall::withReturn 452 ns 452 ns 1575065 +ReflectedCall::withReturn 633 ns 633 ns 1064025 + +StdFuncMethodCall::withReturn 452 ns 451 ns 1575285 +ReflectedMethodCall::withReturn 640 ns 640 ns 1059786 +----------------------------------- +>>> Run 2: workload scale = 25 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 25 iterations +============================================= + +2025-09-11T00:44:56+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3794.29 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.72, 1.40, 1.08 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 317 ns 317 ns 2228177 +StdFuncCall::noReturn 308 ns 308 ns 2227891 +ReflectedCall::noReturn 318 ns 318 ns 2185558 + +StdFuncMethodCall::noReturn 320 ns 320 ns 2209955 +ReflectedMethodCall::noReturn 324 ns 324 ns 2131421 +-------------------------------------------------------------------------- +DirectCall::withReturn 445 ns 445 ns 1580444 +StdFuncCall::withReturn 457 ns 457 ns 1550760 +ReflectedCall::withReturn 633 ns 633 ns 1077338 + +StdFuncMethodCall::withReturn 446 ns 446 ns 1563643 +ReflectedMethodCall::withReturn 646 ns 646 ns 1082286 +----------------------------------- +>>> Run 3: workload scale = 25 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 25 iterations +============================================= + +2025-09-11T00:45:06+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2203.19 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.77, 1.42, 1.09 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 312 ns 312 ns 2244981 +StdFuncCall::noReturn 308 ns 308 ns 2262484 +ReflectedCall::noReturn 318 ns 318 ns 2253123 + +StdFuncMethodCall::noReturn 312 ns 312 ns 2223455 +ReflectedMethodCall::noReturn 325 ns 325 ns 2208582 +-------------------------------------------------------------------------- +DirectCall::withReturn 515 ns 515 ns 1315012 +StdFuncCall::withReturn 514 ns 514 ns 1314421 +ReflectedCall::withReturn 705 ns 705 ns 996799 + +StdFuncMethodCall::withReturn 518 ns 518 ns 1295521 +ReflectedMethodCall::withReturn 706 ns 706 ns 928961 +----------------------------------- +>>> Run 4: workload scale = 25 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 25 iterations +============================================= + +2025-09-11T00:45:15+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 1438.63 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.72, 1.42, 1.09 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 351 ns 351 ns 2035112 +StdFuncCall::noReturn 349 ns 349 ns 2030713 +ReflectedCall::noReturn 349 ns 349 ns 1980472 + +StdFuncMethodCall::noReturn 349 ns 349 ns 2023659 +ReflectedMethodCall::noReturn 353 ns 353 ns 1978482 +-------------------------------------------------------------------------- +DirectCall::withReturn 510 ns 510 ns 1329271 +StdFuncCall::withReturn 513 ns 513 ns 1337409 +ReflectedCall::withReturn 712 ns 712 ns 931367 + +StdFuncMethodCall::withReturn 511 ns 511 ns 1279105 +ReflectedMethodCall::withReturn 711 ns 711 ns 980263 +----------------------------------- +>>> Run 5: workload scale = 25 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 25 iterations +============================================= + +2025-09-11T00:45:24+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3937.8 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.61, 1.41, 1.09 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 316 ns 316 ns 2233794 +StdFuncCall::noReturn 345 ns 345 ns 2026485 +ReflectedCall::noReturn 354 ns 354 ns 1979604 + +StdFuncMethodCall::noReturn 351 ns 351 ns 1987525 +ReflectedMethodCall::noReturn 363 ns 363 ns 1943031 +-------------------------------------------------------------------------- +DirectCall::withReturn 523 ns 523 ns 1281177 +StdFuncCall::withReturn 522 ns 522 ns 1331638 +ReflectedCall::withReturn 702 ns 702 ns 971089 + +StdFuncMethodCall::withReturn 527 ns 527 ns 1342443 +ReflectedMethodCall::withReturn 707 ns 707 ns 966971 +----------------------------------- +>>> Run 1: workload scale = 50 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 50 iterations +============================================= + +2025-09-11T00:45:34+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3864.23 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.52, 1.39, 1.09 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 907 ns 906 ns 671085 +StdFuncCall::noReturn 913 ns 913 ns 750555 +ReflectedCall::noReturn 927 ns 927 ns 739691 + +StdFuncMethodCall::noReturn 913 ns 913 ns 734242 +ReflectedMethodCall::noReturn 938 ns 938 ns 722635 +-------------------------------------------------------------------------- +DirectCall::withReturn 1246 ns 1246 ns 536846 +StdFuncCall::withReturn 1268 ns 1268 ns 546493 +ReflectedCall::withReturn 1595 ns 1595 ns 423002 + +StdFuncMethodCall::withReturn 1240 ns 1240 ns 548835 +ReflectedMethodCall::withReturn 1533 ns 1533 ns 444235 +----------------------------------- +>>> Run 2: workload scale = 50 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 50 iterations +============================================= + +2025-09-11T00:45:42+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4198.35 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.56, 1.40, 1.10 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 897 ns 897 ns 725976 +StdFuncCall::noReturn 907 ns 907 ns 753162 +ReflectedCall::noReturn 928 ns 927 ns 742010 + +StdFuncMethodCall::noReturn 919 ns 919 ns 716837 +ReflectedMethodCall::noReturn 939 ns 939 ns 713611 +-------------------------------------------------------------------------- +DirectCall::withReturn 1199 ns 1199 ns 561452 +StdFuncCall::withReturn 1197 ns 1197 ns 560668 +ReflectedCall::withReturn 1530 ns 1529 ns 426508 + +StdFuncMethodCall::withReturn 1209 ns 1209 ns 556128 +ReflectedMethodCall::withReturn 1540 ns 1540 ns 434049 +----------------------------------- +>>> Run 3: workload scale = 50 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 50 iterations +============================================= + +2025-09-11T00:45:50+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3711.21 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.47, 1.39, 1.09 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 906 ns 906 ns 730770 +StdFuncCall::noReturn 904 ns 904 ns 752526 +ReflectedCall::noReturn 924 ns 924 ns 717981 + +StdFuncMethodCall::noReturn 899 ns 899 ns 738039 +ReflectedMethodCall::noReturn 923 ns 923 ns 726660 +-------------------------------------------------------------------------- +DirectCall::withReturn 1172 ns 1172 ns 563431 +StdFuncCall::withReturn 1187 ns 1186 ns 559493 +ReflectedCall::withReturn 1525 ns 1525 ns 444023 + +StdFuncMethodCall::withReturn 1180 ns 1180 ns 564379 +ReflectedMethodCall::withReturn 1540 ns 1540 ns 444693 +----------------------------------- +>>> Run 4: workload scale = 50 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 50 iterations +============================================= + +2025-09-11T00:45:58+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4048.1 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.55, 1.41, 1.10 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 899 ns 899 ns 744944 +StdFuncCall::noReturn 905 ns 905 ns 755918 +ReflectedCall::noReturn 910 ns 910 ns 735203 + +StdFuncMethodCall::noReturn 910 ns 910 ns 751759 +ReflectedMethodCall::noReturn 927 ns 927 ns 718062 +-------------------------------------------------------------------------- +DirectCall::withReturn 1193 ns 1192 ns 571996 +StdFuncCall::withReturn 1196 ns 1196 ns 551911 +ReflectedCall::withReturn 1509 ns 1509 ns 431152 + +StdFuncMethodCall::withReturn 1191 ns 1191 ns 546073 +ReflectedMethodCall::withReturn 1537 ns 1537 ns 441082 +----------------------------------- +>>> Run 5: workload scale = 50 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 50 iterations +============================================= + +2025-09-11T00:46:06+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 1023.61 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.51, 1.40, 1.10 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 916 ns 916 ns 732935 +StdFuncCall::noReturn 923 ns 923 ns 738825 +ReflectedCall::noReturn 920 ns 920 ns 716561 + +StdFuncMethodCall::noReturn 909 ns 909 ns 733723 +ReflectedMethodCall::noReturn 939 ns 939 ns 712708 +-------------------------------------------------------------------------- +DirectCall::withReturn 1189 ns 1188 ns 567232 +StdFuncCall::withReturn 1190 ns 1190 ns 556865 +ReflectedCall::withReturn 1531 ns 1531 ns 454023 + +StdFuncMethodCall::withReturn 1187 ns 1187 ns 586934 +ReflectedMethodCall::withReturn 1536 ns 1536 ns 448828 +----------------------------------- +>>> Run 1: workload scale = 75 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 75 iterations +============================================= + +2025-09-11T00:46:14+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4506.7 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.51, 1.41, 1.11 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1711 ns 1710 ns 397182 +StdFuncCall::noReturn 1701 ns 1701 ns 392625 +ReflectedCall::noReturn 1707 ns 1707 ns 404729 + +StdFuncMethodCall::noReturn 1708 ns 1708 ns 400940 +ReflectedMethodCall::noReturn 1745 ns 1744 ns 393836 +-------------------------------------------------------------------------- +DirectCall::withReturn 2360 ns 2360 ns 296471 +StdFuncCall::withReturn 2358 ns 2358 ns 293315 +ReflectedCall::withReturn 3057 ns 3057 ns 229041 + +StdFuncMethodCall::withReturn 2355 ns 2354 ns 296620 +ReflectedMethodCall::withReturn 3034 ns 3034 ns 227950 +----------------------------------- +>>> Run 2: workload scale = 75 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 75 iterations +============================================= + +2025-09-11T00:46:23+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 1536.93 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.51, 1.41, 1.11 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1746 ns 1746 ns 395262 +StdFuncCall::noReturn 1734 ns 1733 ns 403023 +ReflectedCall::noReturn 1752 ns 1752 ns 380243 + +StdFuncMethodCall::noReturn 1753 ns 1753 ns 388427 +ReflectedMethodCall::noReturn 1752 ns 1751 ns 388074 +-------------------------------------------------------------------------- +DirectCall::withReturn 2395 ns 2395 ns 289058 +StdFuncCall::withReturn 2433 ns 2433 ns 286696 +ReflectedCall::withReturn 3164 ns 3164 ns 221492 + +StdFuncMethodCall::withReturn 2440 ns 2440 ns 281890 +ReflectedMethodCall::withReturn 3166 ns 3166 ns 221117 +----------------------------------- +>>> Run 3: workload scale = 75 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 75 iterations +============================================= + +2025-09-11T00:46:32+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3018.84 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.43, 1.40, 1.11 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1741 ns 1741 ns 377462 +StdFuncCall::noReturn 1715 ns 1715 ns 398238 +ReflectedCall::noReturn 1722 ns 1721 ns 395238 + +StdFuncMethodCall::noReturn 1740 ns 1740 ns 391977 +ReflectedMethodCall::noReturn 1709 ns 1709 ns 391583 +-------------------------------------------------------------------------- +DirectCall::withReturn 2301 ns 2301 ns 303807 +StdFuncCall::withReturn 2312 ns 2311 ns 300175 +ReflectedCall::withReturn 3073 ns 3073 ns 226573 + +StdFuncMethodCall::withReturn 2423 ns 2423 ns 290465 +ReflectedMethodCall::withReturn 3169 ns 3169 ns 224297 +----------------------------------- +>>> Run 4: workload scale = 75 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 75 iterations +============================================= + +2025-09-11T00:46:42+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3748.07 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.56, 1.42, 1.12 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1762 ns 1762 ns 388500 +StdFuncCall::noReturn 1755 ns 1754 ns 387350 +ReflectedCall::noReturn 1753 ns 1753 ns 380083 + +StdFuncMethodCall::noReturn 1750 ns 1750 ns 393882 +ReflectedMethodCall::noReturn 1776 ns 1776 ns 391549 +-------------------------------------------------------------------------- +DirectCall::withReturn 2401 ns 2400 ns 295056 +StdFuncCall::withReturn 2397 ns 2397 ns 295309 +ReflectedCall::withReturn 3079 ns 3079 ns 222199 + +StdFuncMethodCall::withReturn 2409 ns 2409 ns 282929 +ReflectedMethodCall::withReturn 3179 ns 3179 ns 223918 +----------------------------------- +>>> Run 5: workload scale = 75 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 75 iterations +============================================= + +2025-09-11T00:46:51+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 1163.73 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.63, 1.44, 1.13 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1723 ns 1722 ns 384064 +StdFuncCall::noReturn 1737 ns 1737 ns 391656 +ReflectedCall::noReturn 1731 ns 1730 ns 386459 + +StdFuncMethodCall::noReturn 1733 ns 1733 ns 397046 +ReflectedMethodCall::noReturn 1737 ns 1737 ns 395967 +-------------------------------------------------------------------------- +DirectCall::withReturn 2369 ns 2369 ns 291883 +StdFuncCall::withReturn 2372 ns 2371 ns 291833 +ReflectedCall::withReturn 3078 ns 3078 ns 228938 + +StdFuncMethodCall::withReturn 2363 ns 2363 ns 291230 +ReflectedMethodCall::withReturn 3115 ns 3115 ns 224035 +----------------------------------- +>>> Run 1: workload scale = 100 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 100 iterations +============================================= + +2025-09-11T00:47:01+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4500 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.60, 1.44, 1.13 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1980 ns 1979 ns 351441 +StdFuncCall::noReturn 1949 ns 1949 ns 354766 +ReflectedCall::noReturn 1986 ns 1986 ns 347476 + +StdFuncMethodCall::noReturn 1961 ns 1960 ns 344503 +ReflectedMethodCall::noReturn 1994 ns 1993 ns 343757 +-------------------------------------------------------------------------- +DirectCall::withReturn 3012 ns 3011 ns 236164 +StdFuncCall::withReturn 2976 ns 2976 ns 235449 +ReflectedCall::withReturn 3916 ns 3916 ns 184109 + +StdFuncMethodCall::withReturn 3026 ns 3026 ns 229148 +ReflectedMethodCall::withReturn 3878 ns 3878 ns 181123 +----------------------------------- +>>> Run 2: workload scale = 100 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 100 iterations +============================================= + +2025-09-11T00:47:11+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3869.01 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.59, 1.45, 1.14 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1973 ns 1973 ns 343831 +StdFuncCall::noReturn 1967 ns 1966 ns 336873 +ReflectedCall::noReturn 1989 ns 1989 ns 348169 + +StdFuncMethodCall::noReturn 1988 ns 1988 ns 348304 +ReflectedMethodCall::noReturn 2012 ns 2012 ns 335265 +-------------------------------------------------------------------------- +DirectCall::withReturn 3000 ns 3000 ns 230973 +StdFuncCall::withReturn 2967 ns 2966 ns 232757 +ReflectedCall::withReturn 3876 ns 3876 ns 183695 + +StdFuncMethodCall::withReturn 3027 ns 3027 ns 232206 +ReflectedMethodCall::withReturn 3816 ns 3815 ns 182478 +----------------------------------- +>>> Run 3: workload scale = 100 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 100 iterations +============================================= + +2025-09-11T00:47:20+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4509.87 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.58, 1.45, 1.14 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1987 ns 1987 ns 344531 +StdFuncCall::noReturn 1974 ns 1974 ns 339319 +ReflectedCall::noReturn 1991 ns 1990 ns 341989 + +StdFuncMethodCall::noReturn 1991 ns 1991 ns 345119 +ReflectedMethodCall::noReturn 1994 ns 1993 ns 350809 +-------------------------------------------------------------------------- +DirectCall::withReturn 3024 ns 3023 ns 236080 +StdFuncCall::withReturn 2991 ns 2990 ns 234932 +ReflectedCall::withReturn 3841 ns 3841 ns 182164 + +StdFuncMethodCall::withReturn 2949 ns 2948 ns 237674 +ReflectedMethodCall::withReturn 3890 ns 3890 ns 180491 +----------------------------------- +>>> Run 4: workload scale = 100 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 100 iterations +============================================= + +2025-09-11T00:47:30+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2443.13 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.57, 1.45, 1.14 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1974 ns 1974 ns 347223 +StdFuncCall::noReturn 1973 ns 1972 ns 348490 +ReflectedCall::noReturn 1994 ns 1994 ns 345264 + +StdFuncMethodCall::noReturn 1940 ns 1939 ns 343663 +ReflectedMethodCall::noReturn 2003 ns 2003 ns 348942 +-------------------------------------------------------------------------- +DirectCall::withReturn 2965 ns 2965 ns 237170 +StdFuncCall::withReturn 2913 ns 2913 ns 238241 +ReflectedCall::withReturn 3852 ns 3852 ns 184604 + +StdFuncMethodCall::withReturn 2915 ns 2915 ns 237691 +ReflectedMethodCall::withReturn 3782 ns 3782 ns 185705 +----------------------------------- +>>> Run 5: workload scale = 100 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 100 iterations +============================================= + +2025-09-11T00:47:40+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4409.93 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.56, 1.45, 1.15 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 1982 ns 1982 ns 351034 +StdFuncCall::noReturn 1964 ns 1964 ns 341721 +ReflectedCall::noReturn 1989 ns 1988 ns 350068 + +StdFuncMethodCall::noReturn 1955 ns 1954 ns 348141 +ReflectedMethodCall::noReturn 2003 ns 2003 ns 342525 +-------------------------------------------------------------------------- +DirectCall::withReturn 2953 ns 2953 ns 234331 +StdFuncCall::withReturn 2934 ns 2934 ns 238160 +ReflectedCall::withReturn 3811 ns 3810 ns 186625 + +StdFuncMethodCall::withReturn 2912 ns 2912 ns 236835 +ReflectedMethodCall::withReturn 3899 ns 3898 ns 184770 +----------------------------------- +>>> Run 1: workload scale = 125 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 125 iterations +============================================= + +2025-09-11T00:47:50+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4575.95 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.63, 1.47, 1.16 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2187 ns 2187 ns 313314 +StdFuncCall::noReturn 2203 ns 2202 ns 310954 +ReflectedCall::noReturn 2231 ns 2231 ns 314343 + +StdFuncMethodCall::noReturn 2218 ns 2217 ns 308543 +ReflectedMethodCall::noReturn 2262 ns 2262 ns 307479 +-------------------------------------------------------------------------- +DirectCall::withReturn 3654 ns 3654 ns 191381 +StdFuncCall::withReturn 3651 ns 3650 ns 193595 +ReflectedCall::withReturn 4748 ns 4747 ns 151401 + +StdFuncMethodCall::withReturn 3605 ns 3605 ns 194245 +ReflectedMethodCall::withReturn 4691 ns 4690 ns 152913 +----------------------------------- +>>> Run 2: workload scale = 125 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 125 iterations +============================================= + +2025-09-11T00:48:01+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4523.6 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.69, 1.49, 1.17 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2236 ns 2236 ns 309724 +StdFuncCall::noReturn 2214 ns 2214 ns 315329 +ReflectedCall::noReturn 2236 ns 2236 ns 309095 + +StdFuncMethodCall::noReturn 2217 ns 2217 ns 304364 +ReflectedMethodCall::noReturn 2247 ns 2247 ns 313155 +-------------------------------------------------------------------------- +DirectCall::withReturn 3592 ns 3592 ns 195293 +StdFuncCall::withReturn 3587 ns 3587 ns 194462 +ReflectedCall::withReturn 4691 ns 4691 ns 152353 + +StdFuncMethodCall::withReturn 3594 ns 3594 ns 196395 +ReflectedMethodCall::withReturn 4737 ns 4737 ns 151845 +----------------------------------- +>>> Run 3: workload scale = 125 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 125 iterations +============================================= + +2025-09-11T00:48:11+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 799.56 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.73, 1.50, 1.18 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2237 ns 2237 ns 301530 +StdFuncCall::noReturn 2248 ns 2248 ns 305381 +ReflectedCall::noReturn 2234 ns 2233 ns 307334 + +StdFuncMethodCall::noReturn 2250 ns 2249 ns 289243 +ReflectedMethodCall::noReturn 2241 ns 2241 ns 308428 +-------------------------------------------------------------------------- +DirectCall::withReturn 3685 ns 3684 ns 190114 +StdFuncCall::withReturn 3669 ns 3668 ns 190878 +ReflectedCall::withReturn 4782 ns 4782 ns 150076 + +StdFuncMethodCall::withReturn 3606 ns 3606 ns 191373 +ReflectedMethodCall::withReturn 4723 ns 4722 ns 152622 +----------------------------------- +>>> Run 4: workload scale = 125 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 125 iterations +============================================= + +2025-09-11T00:48:22+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2959.43 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.77, 1.52, 1.19 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2182 ns 2182 ns 307571 +StdFuncCall::noReturn 2184 ns 2183 ns 313459 +ReflectedCall::noReturn 2205 ns 2205 ns 317597 + +StdFuncMethodCall::noReturn 2224 ns 2223 ns 310829 +ReflectedMethodCall::noReturn 2235 ns 2235 ns 303977 +-------------------------------------------------------------------------- +DirectCall::withReturn 3643 ns 3642 ns 195401 +StdFuncCall::withReturn 3554 ns 3554 ns 196445 +ReflectedCall::withReturn 4606 ns 4605 ns 154556 + +StdFuncMethodCall::withReturn 3581 ns 3581 ns 195507 +ReflectedMethodCall::withReturn 4713 ns 4713 ns 152328 +----------------------------------- +>>> Run 5: workload scale = 125 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 125 iterations +============================================= + +2025-09-11T00:48:32+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3933.71 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.90, 1.56, 1.20 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 2204 ns 2204 ns 305118 +StdFuncCall::noReturn 2204 ns 2204 ns 319369 +ReflectedCall::noReturn 2209 ns 2208 ns 314425 + +StdFuncMethodCall::noReturn 2226 ns 2226 ns 305314 +ReflectedMethodCall::noReturn 2220 ns 2220 ns 310974 +-------------------------------------------------------------------------- +DirectCall::withReturn 3649 ns 3648 ns 195704 +StdFuncCall::withReturn 3665 ns 3664 ns 193630 +ReflectedCall::withReturn 4598 ns 4598 ns 152357 + +StdFuncMethodCall::withReturn 3629 ns 3628 ns 195927 +ReflectedMethodCall::withReturn 4805 ns 4805 ns 154283 +----------------------------------- +>>> Run 1: workload scale = 150 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 150 iterations +============================================= + +2025-09-11T00:48:43+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2671.41 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.92, 1.58, 1.21 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3533 ns 3533 ns 195180 +StdFuncCall::noReturn 3546 ns 3545 ns 195924 +ReflectedCall::noReturn 3625 ns 3625 ns 195820 + +StdFuncMethodCall::noReturn 3629 ns 3629 ns 197819 +ReflectedMethodCall::noReturn 3626 ns 3626 ns 195755 +-------------------------------------------------------------------------- +DirectCall::withReturn 5182 ns 5181 ns 131107 +StdFuncCall::withReturn 5141 ns 5140 ns 130322 +ReflectedCall::withReturn 6380 ns 6380 ns 107050 + +StdFuncMethodCall::withReturn 5139 ns 5137 ns 130221 +ReflectedMethodCall::withReturn 6283 ns 6283 ns 109882 +----------------------------------- +>>> Run 2: workload scale = 150 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 150 iterations +============================================= + +2025-09-11T00:48:52+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 1153.89 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.92, 1.58, 1.22 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3645 ns 3644 ns 194763 +StdFuncCall::noReturn 3536 ns 3536 ns 197643 +ReflectedCall::noReturn 3549 ns 3549 ns 197667 + +StdFuncMethodCall::noReturn 3535 ns 3534 ns 197002 +ReflectedMethodCall::noReturn 3554 ns 3553 ns 196119 +-------------------------------------------------------------------------- +DirectCall::withReturn 5181 ns 5181 ns 135659 +StdFuncCall::withReturn 5179 ns 5178 ns 133046 +ReflectedCall::withReturn 6373 ns 6371 ns 109310 + +StdFuncMethodCall::withReturn 5177 ns 5176 ns 132606 +ReflectedMethodCall::withReturn 6372 ns 6372 ns 109258 +----------------------------------- +>>> Run 3: workload scale = 150 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 150 iterations +============================================= + +2025-09-11T00:49:01+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.93, 1.60, 1.23 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3528 ns 3528 ns 197592 +StdFuncCall::noReturn 3510 ns 3510 ns 191078 +ReflectedCall::noReturn 3526 ns 3526 ns 197562 + +StdFuncMethodCall::noReturn 3514 ns 3513 ns 198411 +ReflectedMethodCall::noReturn 3730 ns 3730 ns 196892 +-------------------------------------------------------------------------- +DirectCall::withReturn 5354 ns 5352 ns 123800 +StdFuncCall::withReturn 5245 ns 5244 ns 136768 +ReflectedCall::withReturn 6398 ns 6397 ns 106337 + +StdFuncMethodCall::withReturn 5175 ns 5175 ns 131618 +ReflectedMethodCall::withReturn 6289 ns 6287 ns 110758 +----------------------------------- +>>> Run 4: workload scale = 150 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 150 iterations +============================================= + +2025-09-11T00:49:10+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2161.83 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.87, 1.59, 1.23 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3542 ns 3541 ns 198073 +StdFuncCall::noReturn 3610 ns 3609 ns 195386 +ReflectedCall::noReturn 3583 ns 3582 ns 195941 + +StdFuncMethodCall::noReturn 3507 ns 3506 ns 199751 +ReflectedMethodCall::noReturn 3609 ns 3608 ns 195357 +-------------------------------------------------------------------------- +DirectCall::withReturn 5074 ns 5073 ns 137709 +StdFuncCall::withReturn 5121 ns 5120 ns 136175 +ReflectedCall::withReturn 6347 ns 6346 ns 107908 + +StdFuncMethodCall::withReturn 5089 ns 5088 ns 132717 +ReflectedMethodCall::withReturn 6358 ns 6358 ns 111927 +----------------------------------- +>>> Run 5: workload scale = 150 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 150 iterations +============================================= + +2025-09-11T00:49:20+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 1932.44 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.73, 1.57, 1.23 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3578 ns 3577 ns 198985 +StdFuncCall::noReturn 3533 ns 3532 ns 198881 +ReflectedCall::noReturn 3517 ns 3517 ns 198702 + +StdFuncMethodCall::noReturn 3572 ns 3572 ns 198500 +ReflectedMethodCall::noReturn 3572 ns 3572 ns 185939 +-------------------------------------------------------------------------- +DirectCall::withReturn 5046 ns 5046 ns 137950 +StdFuncCall::withReturn 5080 ns 5078 ns 138211 +ReflectedCall::withReturn 6306 ns 6304 ns 110192 + +StdFuncMethodCall::withReturn 5132 ns 5132 ns 131855 +ReflectedMethodCall::withReturn 6343 ns 6343 ns 107271 +----------------------------------- +>>> Run 1: workload scale = 175 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 175 iterations +============================================= + +2025-09-11T00:49:29+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2271.38 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.62, 1.55, 1.22 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3789 ns 3788 ns 183993 +StdFuncCall::noReturn 3777 ns 3776 ns 185241 +ReflectedCall::noReturn 3781 ns 3780 ns 183887 + +StdFuncMethodCall::noReturn 3778 ns 3777 ns 185194 +ReflectedMethodCall::noReturn 3815 ns 3814 ns 183316 +-------------------------------------------------------------------------- +DirectCall::withReturn 5771 ns 5770 ns 121611 +StdFuncCall::withReturn 5735 ns 5734 ns 117886 +ReflectedCall::withReturn 7182 ns 7182 ns 94871 + +StdFuncMethodCall::withReturn 5796 ns 5795 ns 116975 +ReflectedMethodCall::withReturn 7194 ns 7193 ns 94083 +----------------------------------- +>>> Run 2: workload scale = 175 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 175 iterations +============================================= + +2025-09-11T00:49:38+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2817.81 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.52, 1.54, 1.22 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3877 ns 3877 ns 180661 +StdFuncCall::noReturn 3876 ns 3876 ns 181752 +ReflectedCall::noReturn 3864 ns 3864 ns 180348 + +StdFuncMethodCall::noReturn 3914 ns 3914 ns 180868 +ReflectedMethodCall::noReturn 3853 ns 3853 ns 179813 +-------------------------------------------------------------------------- +DirectCall::withReturn 5826 ns 5826 ns 120405 +StdFuncCall::withReturn 5768 ns 5768 ns 120615 +ReflectedCall::withReturn 7147 ns 7147 ns 97322 + +StdFuncMethodCall::withReturn 5783 ns 5783 ns 120537 +ReflectedMethodCall::withReturn 7177 ns 7176 ns 96515 +----------------------------------- +>>> Run 3: workload scale = 175 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 175 iterations +============================================= + +2025-09-11T00:49:48+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2468.99 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.44, 1.52, 1.22 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3867 ns 3866 ns 184722 +StdFuncCall::noReturn 3813 ns 3813 ns 182052 +ReflectedCall::noReturn 3799 ns 3799 ns 183532 + +StdFuncMethodCall::noReturn 3901 ns 3901 ns 180895 +ReflectedMethodCall::noReturn 3821 ns 3820 ns 180911 +-------------------------------------------------------------------------- +DirectCall::withReturn 5694 ns 5694 ns 122739 +StdFuncCall::withReturn 5726 ns 5726 ns 122179 +ReflectedCall::withReturn 7126 ns 7125 ns 99356 + +StdFuncMethodCall::withReturn 5669 ns 5669 ns 120489 +ReflectedMethodCall::withReturn 7014 ns 7013 ns 94851 +----------------------------------- +>>> Run 4: workload scale = 175 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 175 iterations +============================================= + +2025-09-11T00:49:57+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 800 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.37, 1.50, 1.21 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3811 ns 3811 ns 185570 +StdFuncCall::noReturn 4045 ns 4045 ns 173991 +ReflectedCall::noReturn 3913 ns 3913 ns 178008 + +StdFuncMethodCall::noReturn 3858 ns 3858 ns 180877 +ReflectedMethodCall::noReturn 3897 ns 3897 ns 181085 +-------------------------------------------------------------------------- +DirectCall::withReturn 5706 ns 5706 ns 118455 +StdFuncCall::withReturn 5694 ns 5693 ns 119512 +ReflectedCall::withReturn 7093 ns 7093 ns 95014 + +StdFuncMethodCall::withReturn 5716 ns 5715 ns 117328 +ReflectedMethodCall::withReturn 7107 ns 7107 ns 92531 +----------------------------------- +>>> Run 5: workload scale = 175 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 175 iterations +============================================= + +2025-09-11T00:50:07+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 2389.7 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.34, 1.49, 1.21 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 3823 ns 3822 ns 184164 +StdFuncCall::noReturn 3921 ns 3920 ns 184054 +ReflectedCall::noReturn 3892 ns 3892 ns 181641 + +StdFuncMethodCall::noReturn 3917 ns 3917 ns 182002 +ReflectedMethodCall::noReturn 3937 ns 3937 ns 179719 +-------------------------------------------------------------------------- +DirectCall::withReturn 5706 ns 5706 ns 116926 +StdFuncCall::withReturn 5727 ns 5726 ns 122669 +ReflectedCall::withReturn 7154 ns 7153 ns 92948 + +StdFuncMethodCall::withReturn 5696 ns 5695 ns 117101 +ReflectedMethodCall::withReturn 7157 ns 7157 ns 94837 +----------------------------------- +>>> Run 1: workload scale = 200 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 200 iterations +============================================= + +2025-09-11T00:50:16+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3209.87 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.37, 1.49, 1.22 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 4196 ns 4196 ns 170841 +StdFuncCall::noReturn 4197 ns 4197 ns 171474 +ReflectedCall::noReturn 4213 ns 4213 ns 167685 + +StdFuncMethodCall::noReturn 4195 ns 4195 ns 169093 +ReflectedMethodCall::noReturn 4235 ns 4235 ns 167817 +-------------------------------------------------------------------------- +DirectCall::withReturn 6403 ns 6401 ns 105705 +StdFuncCall::withReturn 6411 ns 6409 ns 104979 +ReflectedCall::withReturn 7984 ns 7982 ns 83508 + +StdFuncMethodCall::withReturn 6531 ns 6529 ns 106182 +ReflectedMethodCall::withReturn 7987 ns 7986 ns 83759 +----------------------------------- +>>> Run 2: workload scale = 200 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 200 iterations +============================================= + +2025-09-11T00:50:26+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4359.42 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.47, 1.51, 1.22 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 4189 ns 4188 ns 170733 +StdFuncCall::noReturn 4075 ns 4073 ns 173783 +ReflectedCall::noReturn 4062 ns 4062 ns 172155 + +StdFuncMethodCall::noReturn 4036 ns 4036 ns 172871 +ReflectedMethodCall::noReturn 4156 ns 4156 ns 171048 +-------------------------------------------------------------------------- +DirectCall::withReturn 6255 ns 6255 ns 107881 +StdFuncCall::withReturn 6202 ns 6201 ns 111269 +ReflectedCall::withReturn 7732 ns 7731 ns 88927 + +StdFuncMethodCall::withReturn 6237 ns 6237 ns 108573 +ReflectedMethodCall::withReturn 7717 ns 7716 ns 87408 +----------------------------------- +>>> Run 3: workload scale = 200 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 200 iterations +============================================= + +2025-09-11T00:50:35+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3349.71 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.47, 1.51, 1.23 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 4183 ns 4182 ns 172969 +StdFuncCall::noReturn 4158 ns 4158 ns 169661 +ReflectedCall::noReturn 4320 ns 4320 ns 167031 + +StdFuncMethodCall::noReturn 4142 ns 4142 ns 171242 +ReflectedMethodCall::noReturn 4244 ns 4243 ns 167943 +-------------------------------------------------------------------------- +DirectCall::withReturn 6215 ns 6214 ns 107561 +StdFuncCall::withReturn 6285 ns 6284 ns 106520 +ReflectedCall::withReturn 7794 ns 7794 ns 86470 + +StdFuncMethodCall::withReturn 6259 ns 6257 ns 109244 +ReflectedMethodCall::withReturn 7877 ns 7876 ns 83397 +----------------------------------- +>>> Run 4: workload scale = 200 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 200 iterations +============================================= + +2025-09-11T00:50:45+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 4518.55 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.56, 1.52, 1.24 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 4097 ns 4097 ns 172402 +StdFuncCall::noReturn 4102 ns 4102 ns 171446 +ReflectedCall::noReturn 4218 ns 4217 ns 171091 + +StdFuncMethodCall::noReturn 4080 ns 4080 ns 170058 +ReflectedMethodCall::noReturn 4114 ns 4114 ns 170617 +-------------------------------------------------------------------------- +DirectCall::withReturn 6368 ns 6367 ns 109122 +StdFuncCall::withReturn 6411 ns 6410 ns 104101 +ReflectedCall::withReturn 8033 ns 8032 ns 86564 + +StdFuncMethodCall::withReturn 6449 ns 6448 ns 103769 +ReflectedMethodCall::withReturn 8047 ns 8047 ns 82650 +----------------------------------- +>>> Run 5: workload scale = 200 + +======== RTL Benchmark Configuration ======== +Workload: concatenate string of length 500 +Scale : 200 iterations +============================================= + +2025-09-11T00:50:55+05:30 +Running ./bin/RTLBenchmarkApp +Run on (16 X 3507.94 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x8) + L1 Instruction 32 KiB (x8) + L2 Unified 1280 KiB (x8) + L3 Unified 20480 KiB (x1) +Load Average: 1.55, 1.52, 1.24 +-------------------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------------------- +DirectCall::noReturn 4065 ns 4065 ns 171561 +StdFuncCall::noReturn 4084 ns 4084 ns 174097 +ReflectedCall::noReturn 4206 ns 4205 ns 172296 + +StdFuncMethodCall::noReturn 4229 ns 4229 ns 173534 +ReflectedMethodCall::noReturn 4068 ns 4068 ns 171180 +-------------------------------------------------------------------------- +DirectCall::withReturn 6278 ns 6277 ns 111032 +StdFuncCall::withReturn 6352 ns 6351 ns 106040 +ReflectedCall::withReturn 7900 ns 7898 ns 87218 + +StdFuncMethodCall::withReturn 6349 ns 6349 ns 110877 +ReflectedMethodCall::withReturn 7880 ns 7878 ns 84629 +----------------------------------- +All benchmarks completed. diff --git a/text-design-docs/DESIGN_PRINCIPLES_AND_FEATURES.md b/text-design-docs/DESIGN_PRINCIPLES_AND_FEATURES.md new file mode 100644 index 00000000..a6702599 --- /dev/null +++ b/text-design-docs/DESIGN_PRINCIPLES_AND_FEATURES.md @@ -0,0 +1,121 @@ +### 🪶 No Static Globals, No Macros, No Surprises + +RTL does not rely on: + +* Hidden static registration +* Centralized global registries +* Preprocessor hacks + +Instead, registration is explicit and lazy. + +For each registered type, RTL contributes **two lightweight entries** into its process-local tables: + +* A **lambda wrapper** placed in a scoped `static` `std::vector` and is responsible for making the final call using the actual functor. +* A **raw function pointer** stored in a parallel scoped `static` `std::vector`, used to detect and prevent redundant registrations. + +From there, `rtl::CxxMirror` does not hold onto heavyweight state. It is **as ordinary as any local variable** — you can construct one, keep it alive for the entire application, or discard it after a short-lived query. The `CxxMirror` can be materialized again with the same or different set of types. RTL guarantees that **materializing the same registration statement multiple times** (for example): + +```cpp +rtl::type().member().method("getName").build(&Person::getName); +``` + +will always yield **exactly the same metadata**, without ever admitting redundant lambdas or functors into the static tables. + +> *"Mirrors are **cheap and repeatable**: the metadata is stable, redundant entries are never entertained, and the user remains in full control of a mirror’s lifetime."* + +--- + +### ⚡ Reflective Call Performance + +Reflective calls in RTL are designed to be explicit, predictable, and minimal. The mechanism unfolds in two clear steps: + +1. **Signature Matching** — Each call signature yields a unique type-ID, compared directly against the ID of the lambda-table holding the final call. With a single overload this resolves immediately; if multiple overloads exist, RTL just scans a tiny `std::vector` of candidate IDs. + +2. **Call Dispatch** — Once the correct overload is identified, RTL performs constant-time `std::vector` indexing to retrieve the associated lambda wrapper. This wrapper executes a single hop to the underlying function pointer, forwarding the provided arguments perfectly. + +The net overhead of a reflective call is thus a handful of integer comparisons, one direct `std::vector` access, and one lambda-to-function-pointer indirection. There are no dynamic allocations, RTTI lookups, or hidden metadata traversals at call time. The cost is transparent and limited to exactly what is required for overload resolution and safe forwarding — no more, no less. + +> *"A reflective call in RTL is not free, but its cost is explicit, transparent, and no greater than what you would write by hand."* + +--- + +### 🛡 Exception-Free Guarantee + +RTL is designed to be virtually exception-free. If an exception ever emerges from RTL, it signals that something deeper is wrong. In practice, such exceptions are almost always caused by client/user code and merely propagate through RTL. Internally, only one scenario could theoretically throw: + +* `std::any_cast` — guarded by strict, break-proof type checks that make throwing virtually impossible. + +This is extremely unlikely, but not absolutely impossible — no system is perfect. +For every predictable failure case, RTL returns explicit error codes instead of throwing. +RTL validates all critical assumptions before proceeding, ensuring predictable behavior and eliminating mid-operation surprises. + +> *"Exceptions should never surprise you — in RTL, failures are explicit, validated, and reported as error codes, not as hidden runtime traps."* + +--- + +### 🔒 Const-By-Default Discipline + +RTL enforces a *const-by-default* discipline. All objects **created through reflection** start as *logically-const* — they default to immutability. If no const overload exists, RTL will **automatically fall back** to the non-const overload, since these objects were never originally declared `const`. Explicit `rtl::constCast()` is only required when both const and non-const overloads are present. + +The guiding principle is simple: reflective objects are safe by default, and any mutation must be a conscious, visible decision by the caller. + +At the same time, RTL strictly respects **true-const** objects (e.g., declared-`const` instances or const return values). Such objects remain immutable inside RTL — any attempt to force mutation results in predictable error code (`rtl::error::IllegalConstCast`). + +> *"RTL never mutates true-const objects, and for RTL-created ones it defaults to const, falling back only if needed — explicit rtl::constCast() is required when both overloads exist."* + +This discipline complements RTL’s exception-free guarantee, ensuring both **predictability** and **safety** at the API boundary. + +--- + +### 🎁 Transparent Handling of Smart Pointers + +Reflection should never feel like a cage. +In everyday C++, if you hold a `std::unique_ptr` or `std::shared_ptr`, you don’t think twice about how to use it — you simply work with the object it points to, sometimes copying it, sometimes sharing it, sometimes moving it. RTL extends this same natural experience into runtime reflection. + +Every heap object created through RTL is safely managed inside a smart pointer. Yet to you, as the developer, that detail is invisible. You can look at it as the smart pointer if you wish, or simply as the underlying type `T`. + +When you ask RTL to clone, it adapts to the situation in the most intuitive way: + +* If a type is naturally shared, you can get a shared view. +* If it is unique, RTL respects that uniqueness. +* And if the value itself can be copied, you can always ask for a fresh independent object. + +The key idea is that RTL doesn’t force you into a wrapper-first mindset. Instead, it makes wrappers feel transparent — you can still reason in terms of *your type*, just as you would in normal C++. + +> *"Developers shouldn’t have to think about “reflection semantics” versus “normal C++ semantics.” With RTL, the two worlds are aligned. Whether you’re holding a raw object or a smart pointer, the same intuition applies — reflection just works the way you expect."* + +--- + +### 🧠 Tooling-Friendly Architecture + +**RTL** separates the *generation* of reflection metadata from its *consumption*. This makes it ideal not just for runtime introspection, but also for external tools like: + +* Code generators +* Serialization pipelines +* Game or UI editors +* Live scripting or plugin systems + +#### ✨ The Mirror & The Reflection + +> *A client system hands off a `CxxMirror` to RTL — and RTL sees its reflection.* + +That’s it. The mirror is a **single object**, typically returned from a function like: + +```cpp +extern const rtl::CxxMirror& MyReflection(); +``` + +This function is: + +* **Externally linkable** — can live in any translation unit or even dynamic module +* **Lazy** — doesn’t require metadata unless explicitly accessed +* **Pure** — returns a complete, immutable view of reflection metadata + +#### 📎 Why This Matters for Tooling + +This design turns RTL into a **pluggable, runtime-agnostic consumer** of metadata. You can: + +* Reflect types from external libraries +* Link in auto-generated metadata modules +* Expose your reflection system to scripts or tools without tight coupling +* Swap different `CxxMirror` sources depending on build mode (dev/editor/runtime) diff --git a/text-design-docs/RTL_SYNTAX_AND_SEMANTICS.md b/text-design-docs/RTL_SYNTAX_AND_SEMANTICS.md new file mode 100644 index 00000000..b1487991 --- /dev/null +++ b/text-design-docs/RTL_SYNTAX_AND_SEMANTICS.md @@ -0,0 +1,514 @@ +# RTL at a Glance: Syntax & Semantics ⚡ + +RTL makes C++ reflection feel like a natural extension of the language. Let’s explore its syntax and the semantics it unlocks. +This guide walks you step by step through RTL’s reflection syntax. + +### 📖 Index + +1. [Building the Mirror 🪞](#building-the-mirror-) +2. [Getting Started with Registration 📝](#getting-started-with-registration-) +3. [Reflective Invocations with RTL ✨](#reflective-invocations-with-rtl-) + + * [Querying C-Style Functions 🔍](#querying-c-style-functions) + * [Performing Reflective Calls ⚙️](#performing-reflective-calls) + * [Extracting Return Values 📤](#extracting-return-values) + * [Querying Member Functions 👤](#querying-member-functions) + * [Binding an Object and Calling 🔗](#binding-an-object-and-calling) + * [Binding Signatures and Perfect Forwarding 🎯](#binding-signatures-and-perfect-forwarding) + * [Const vs Non-Const Method Binding ⚡](#const-vs-non-const-method-binding) +4. [Const-by-Default Discipline 🛡️](#const-by-default-discipline) +5. [Reflective Construction and Destruction 🏗️](#reflective-construction-and-destruction) +6. [Move Semantics in RTL 🔀](#move-semantics-in-rtl) + +--- + +## Building the Mirror 🪞 + +Before registering anything, you need a central place to hold all reflection metadata: the `rtl::CxxMirror`. You can create an instance, passing all type metadata through an initializer list — each type obtained via `rtl::type()`. + +```cpp + auto cxx_mirror = rtl::CxxMirror({ + // .. all the registrations go here, comma separated .. + }); +``` + +Every registration statement you add here is collected into the `rtl::CxxMirror` as an `rtl::Function` object. The `CxxMirror` forms the backbone of RTL. Every type, function, or method you register ultimately gets encapsulated into this single object, serving as the gateway to query, introspect, and instantiate all registered types at runtime. + +### A few key points about managing this object + +* ***Dispensable by design*** → The `CxxMirror` itself carries no hidden global state. You can define one central mirror, create multiple mirrors in different scopes, or even rebuild mirrors on demand. RTL imposes no restriction on how you manage its lifetime. + +* ***Duplicate registration is harmless*** → Identical registrations always materialize the same metadata. If a canonical function pointer is already registered, it is not added again to the lambda/functor table — the metadata simply refers back to the existing entry. + +* ***Thread-safety guaranteed by RTL*** → No matter how you choose to manage mirrors (singleton, multiple, or transient), RTL itself guarantees synchronized, race-free registration and access across threads. + +* ***Overhead is deliberate*** → Each registration carries a small cost in memory and initialization time. Concretely: + * Every registration statement acquires a lock on the functor table. + * It checks whether the function or lambda is already present. + * If not, it adds the new entry to the lambda table and updates the functor table. + +This ensures thread-safety and prevents redundant entries. While negligible for isolated registrations, this cost can accumulate when creating many mirrors or registering large numbers of types. + +👉 Bottom Line +> *"Manage `CxxMirror` however your design requires — singleton, multiple, or transient. Each registration incurs a lock and table lookup, but the cost is negligible in normal use and only noticeable when scaling to very large numbers of types."* + +--- + +## Getting Started with Registration 📝 + +The fundamental pattern of registration in RTL is a **builder combination**. You chain together parts to declare what you are reflecting, and then call `.build()` to complete it. + +### Non-Member Functions + +```cpp +rtl::type().ns("ext").function<..signature..>("func").build(ptr); +``` + +* **`ns("ext")`**: specifies the namespace under which the function lives. Omitting `.ns()` or passing an empty string `.ns("")` keeps the function in the global namespace. +* **`function<..signature..>("func")`**: declares the function by name. If overloaded, the template parameter `<..signature..>` disambiguates which overload to pick. +* **`.build(ptr)`**: supplies the actual function pointer to complete the registration. + +### Handling Overloads + +If multiple overloads exist, you must specify the signature in the template argument. Otherwise, the compiler cannot resolve which function pointer you mean. + +For example: + +```cpp + +bool sendMessage(const char*); +void sendMessage(int, std::string); + +rtl::type().ns("ext").function("sendMessage").build(ext::sendMessage); +rtl::type().ns("ext").function("sendMessage").build(ext::sendMessage); +``` + +### Classes / Structs + +```cpp +rtl::type().ns("ext").record("Name").build(); +``` + +* Registers a type by reflective name under a namespace. +* This step is **mandatory** to register any of its members. +* Default, copy, and move constructors, along with the destructor, are automatically registered. Explicit registration of these special members is disallowed and will result in a compile error. + +### Constructors + +```cpp +rtl::type().member().constructor<..signature..>().build(); +``` + +* **`.member()`**: enters the scope of class/struct `T`. +* **`.constructor<..signature..>()`**: registers a user-defined constructor. The template parameter `<..signature..>` must be provided since no function pointer is available for deduction, and this also disambiguates overloads. + +### Member Functions + +```cpp +rtl::type().member().method<..signature..>("method").build(&T::f); +``` + +* **`.member()`**: enters the scope of class/struct `T`. +* **`.method<..signature..>(...)`**: registers a non-const member function. The template parameter `<..signature..>` disambiguates overloads. +* Variants exist for const (`.methodConst`) and static (`.methodStatic`) methods. + +👉 **Note:** +> ***The `function<..signature..>` and `method<..signature..>` template parameters are primarily for overload resolution. They tell RTL exactly which overload of a function or method you mean to register.*** + +With these constructs—namespaces, non-member functions, overloads, records `(class/struct)`, constructors, and methods—you now have the full registration syntax for RTL. Together, they let you build a complete reflective model of your C++ code. + +--- + +## Reflective Invocations with RTL ✨ + +Discover how to query, invoke, and manipulate functions and objects at runtime using RTL’s powerful reflection API. +Once a function is registered in `rtl::CxxMirror`, you can query it and perform reflective calls dynamically. + + + +### Querying C-Style Functions 🔍 + +```cpp +// Function without a namespace +std::optional popMessage = cxx::mirror().getFunction("popMessage"); + +// Function registered with a namespace +std::optional sendMessage = cxx::mirror().getFunction("utils", "sendMessage"); +``` + +* If a function is registered **without a namespace**, it can only be retrieved without specifying a namespace. +* If a function is registered **with a namespace**, it **must** be queried with the correct namespace. +* The returned value is an `std::optional`. If the function is not found, the optional is empty. + +```cpp +if (popMessage) +{ + // function exists, safe to invoke +} +``` + +--- + + + +### Performing Reflective Calls ⚙️ + +Once you have a `rtl::Function`, a complete reflective call involves two steps: + +```cpp +auto [err, retObj] = popMessage->bind().call(); +``` + +* **`.bind<>()`**: Associates an object for member functions and allows explicit specification of the **signature** of the arguments to be forwarded. For non-member functions, you can simply call `.bind()` without arguments. +* **`.call(args...)`**: Executes the function with the provided arguments. + +Every reflective call returns a `std::pair`: + +* `rtl::error` indicates whether the call was successful (`rtl::error::None`) or if an error occurred. + + * `rtl::error::SignatureMismatch` → provided arguments/signature don’t match with expected signature or any overload. +* `rtl::RObject` contains the return value if the function returns something, or is empty if the function returns `void`. + +--- + + + +### Extracting Return Values 📤 + +```cpp +if (err == rtl::error::None) +{ + if (!retObj.isEmpty() && retObj.canViewAs()) + { + std::optional> viewStr = retObj.view(); + std::string retStr = viewStr->get(); // fully-typed returned string + } +} +``` + +* Return Handling Summary + +When dealing with `rtl::RObject` results: + +| Function | Purpose | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------- | +| `isEmpty()` | Checks if the function returned anything (i.e., non-`void`). | +| `canViewAs()` | Quick type check: returns `true` if the stored type is exactly `T` or safely convertible. | +| `view()` | Retrieves a typed **view** of the stored value if possible. Returns an empty `std::optional` if the type doesn’t match. | +| `view()->get()` | Extracts a const reference or value of `T` from the view, safely typed. | + +👉 **Tip** + +> ***Use `canViewAs()` for a cheap boolean check when branching, and `view()` when you actually need the value.*** + +--- + + + +### Querying Member Functions 👤 + +Member functions require an instance of the class to call upon. RTL provides a two-step process: first retrieve the `rtl::Record` for the type, then get the `rtl::Method` from that record. + +```cpp +// Retrieve the record for the class +std::optional classPerson = cxx::mirror().getRecord("Person"); + +if (classPerson) +{ + // Retrieve a specific method from the record + std::optional setProfile = classPerson->getMethod("setProfile"); + + if (setProfile) + { + // You can now bind an object and call the method + } +} +``` + +* `getRecord("TypeName")` returns the registered class/struct as `rtl::Record`. +* `getMethod("methodName")` retrieves a member function from the record. Returns `std::optional`. +* An empty optional indicates the method was not found. + +--- + + + +### Binding an Object and Calling 🔗 + +```cpp +auto [err, retObj] = setProfile->bind(targetObj).call(std::string("Developer")); +``` + +* **`.bind(targetObj)`**: binds the target instance for the method. + + * `targetObj` is an `rtl::RObject` instance representing the object. + * You can create this instance reflectively using the `rtl::Record`’s constructor (we’ll cover this shortly). +* **`.call(args...)`**: executes the method on the bound object with the provided arguments. + +Errors specific to member function calls: + +* `rtl::error::TargetMismatch` → when the bound `RObject` does not represent the same type as the method’s owning class. +* `rtl::error::EmptyTarget` → when attempting to bind an empty `RObject`. +* `rtl::error::SignatureMismatch` → provided arguments/signature don’t match with expected signature or any overload. + +--- + + + +### Binding Signatures and Perfect Forwarding 🎯 + +```cpp +setProfile->bind(targetObj).call(10); // 10 forwarded as int +setProfile->bind(targetObj).call(10); // 10 forwarded as double (10.0) +setProfile->bind(targetObj).call(10); // compile-time error +``` + +* The template parameter in `bind<..signature..>()` tells RTL how to perceive and forward the arguments. +* RTL uses the template signature to ***figure out*** which method (and which overload, if multiple exist) to select from the registration. +* All arguments are forwarded as universal references (`&&`), enabling **perfect forwarding** with **no copies**. Arguments are ultimately received exactly as the registered function expects (`lvalue`, `rvalue`, `const-lvalue-ref`). +* `rtl::RObject` contains the return value, or is empty if the method returns `void`. + +> ***By retrieving a `Method` from a `Record`, binding a target instance, and specifying the signature as needed, RTL allows safe, perfectly-forwarded reflective calls on member functions.*** + +--- + + + +### Const vs Non-Const Method Binding ⚡ + +When binding methods reflectively, RTL enforces const-correctness in a way that mirrors C++ itself, but with an extra layer of runtime safety. Let’s walk through how this works. + +#### Default Behavior + +Whenever both `const` and `non-const` overloads of a method exist, RTL prefers the **const overload**. This is consistent with RTL’s *const-by-default* philosophy: reflective calls always begin from the safest stance possible. + +```cpp +Person john("John"); +rtl::RObject robj = rtl::type(john); // Reflect object with statically-type; details covered later. + +// If both overloads exist, RTL selects the const one. +auto [err, ret] = someMethod->bind(robj).call(); +``` + +#### Choosing the Non-Const Path + +Sometimes you really do want the non-const overload. RTL requires you to be explicit in that case, by using `rtl::constCast()`: + +```cpp +auto [err, ret] = someMethod->bind(rtl::constCast(robj)).call(); +``` + +This signals intent clearly: *“Treat this object as non-const for this call.”* If the object is safe to cast, RTL allows it. + +#### Fallback to Non-Const + +If a class only defines a non-const method and no const variant exists, RTL will safely fall back and bind to the non-const overload. No extra steps are required, and this remains safe so long as the object wasn’t originally declared `const`. + +#### Declared-Const Objects + +Things change when the reflected object itself was declared `const` in the first place: + +```cpp +const Person constSam("Const-Sam"); // Reflect 'const' with statically-type; details covered later. +rtl::RObject robj = rtl::type(constSam); +``` + +Here, RTL preserves that constness strictly. Non-const methods cannot be invoked on such an object. Attempts to do so will result in `rtl::error::IllegalConstCast`. + +If you attempt a method where **no const overload exists**, RTL reports `rtl::error::ConstOverloadMissing`. + +#### Checking Provenance + +Because reflective calls may hand back new `RObject`s, you may sometimes wonder whether an object is safe to cast. That’s what `isConstCastSafe()` is for: + +```cpp +bool safe = robj.isConstCastSafe(); +``` + +* `false` → The object was originally declared const; treating it as mutable is unsafe. +* `true` → The object wasn’t originally const; RTL may relax constness internally if needed. + +#### Error Codes + +* **None** → Success; call resolved safely. +* **ConstOverloadMissing** → A const-qualified overload was required but not found. +* **NonConstOverloadMissing** → A non-const overload was explicitly requested but not found. +* **IllegalConstCast** → Attempted to cast away `const` from a true-const object. + +#### Summary + +* RTL defaults to the const overload when both exist. +* Explicitly request the non-const overload with `rtl::constCast()`. +* If only non-const exists, RTL uses it safely (unless the object was declared const). +* Declared-const objects reject non-const calls (`rtl::error::IllegalConstCast`) and fail if no const overload is present (`rtl::error::ConstOverloadMissing`). +* `isConstCastSafe()` tells you whether relaxation is permitted. +* Reflective objects are always const-first; declared-const objects are strictly immutable. + +--- + + +## Const-by-Default Discipline 🛡️ + +C++ treats **const** as a contract: a `const` object can only invoke `const` methods, and any attempt to mutate it without an explicit `const_cast` leads to undefined behavior. A non-const object, by contrast, freely chooses non-const overloads but can fall back to const ones when needed. + +RTL mirrors this model but strengthens it with **provenance-aware constness**. In other words, RTL distinguishes between objects it created itself and objects provided externally, applying rules that match their origin. + +### Two Kinds of Constness in RTL + +* **Logically-Const (RTL-Created)** + + * Objects constructed reflectively—whether on the stack or heap—are treated as *const-first*. + * If a non-const overload is the only option, RTL may safely apply an internal `const_cast` because these objects were never originally declared `const`. + * Users can still opt into non-const explicitly via `rtl::constCast()` if both overloads exist. + +* **True-Const (Externally Provided)** + + * Objects passed into RTL with declared `const` remain **strictly const**. + * RTL will never cast them internally, ensuring you can’t accidentally mutate something the compiler itself forbids. + * Missing const overloads result in `rtl::error::ConstOverloadMissing`. Forcing a non-const call results in `rtl::error::IllegalConstCast`. + +### Quick Comparison + +| Case | Native C++ | RTL Behavior | +| -------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Const object** | Only const overload allowed; non-const requires cast; mutation is UB. | **True-const**: only const overload allowed; missing const → `ConstOverloadMissing`; forcing non-const → `IllegalConstCast`. | +| **Non-const object** | Prefers non-const overload, but may call const if that’s the only one. | **Logically-const**: defaults to const; missing const but non-const present → safe fallback; both present → explicit non-const via `rtl::constCast()`. | + +✅ Key Takeaway + +RTL codifies C++’s const rules at runtime: + +* **True-const** objects are strictly immutable. +* **Logically-const** objects default to immutability but can be safely relaxed when overload resolution requires it. + +This makes overload resolution **predictable, safe, and explicit**, giving you runtime reflection that behaves like C++—but with added clarity. + +--- + + +## Reflective Construction and Destruction 🏗️ + +Reflection in RTL doesn’t stop at functions and methods — you can also create full-fledged objects at runtime, directly through their reflected constructors. Cleanup, on the other hand, is fully automatic thanks to C++’s RAII. + +### Constructing Objects + +To construct a reflected object, first grab the `Record` that represents the type, then call one of its `create` helpers: + +```cpp +std::optional classPerson = cxx::mirror().getRecord("Person"); + +// Default constructor — create on heap +auto [err, person] = classPerson->create(); +if (err == rtl::error::None) +{ + // construction successful, use object to call methods now... +} + +// Overloaded constructor — this time create on stack +auto [err, person] = classPerson->create( + std::string("John Doe"), + 42 +); +``` + +Key takeaways: + +* Allocation policy is always explicit — you decide `Heap` or `Stack`. +* Creation returns `[rtl::error, rtl::RObject]`. +* If construction fails, `error != rtl::error::None` and the `RObject` will be empty. +* `rtl::error::SignatureMismatch` if provided arguments/signature don’t match with expected signature or any overload. +* `RObject` is the type-erased container that can hold either: + + * An instance created via a reflected constructor. + * A return value from any reflected call (as we have already seen earlier). + +### Destruction Semantics + +RTL does **not** give you a “destroy” API. All lifetime management is pure **RAII**: + +* **Heap objects** → wrapped in `std::unique_ptr`, destroyed automatically when the owning `RObject` goes out of scope. +* **Stack objects** → destroyed at scope exit like any local variable. +* **Return values** → temporary values that follow normal C++ value semantics. + +This design is intentional: + +* No risk of manual double-free or dangling references. +* Mirrors idiomatic C++ usage — you never call destructors explicitly in regular code, and you don’t here either. + +**Bottom line:** you never destroy a reflected object yourself — RAII does it for you. + +### Creating Reflected Objects With Static-Type + +Besides constructing objects via reflective calls (`create()` or `create()`), RTL also lets you create an `RObject` by **reflecting an existing object**: + +```cpp +Person mutableSam("Mutable-Sam"); +const Person constSam("Const-Sam"); + +rtl::RObject robj1 = rtl::type(mutableSam); +rtl::RObject robj2 = rtl::type(constSam); +``` + +* This always creates a **copy on the stack** inside the `RObject`. +* These stack-based reflections are **scope bound** and never heap-managed. +* Useful for **testing**, since you can quickly reflect arbitrary statically-typed objects. + +--- + + +## Move Semantics in RTL 🔀 + +Let’s walk you through how **move semantics** work in RTL. Since `rtl::RObject` is **move-only** (copying is disallowed), moving objects is the primary way ownership is transferred. The behavior differs depending on whether the object was created on the **stack** or the **heap**. + +### Moving Stack-Allocated Objects + +When you create an object reflectively with `alloc::Stack`, the underlying instance lives directly inside the `RObject`. Moving such an `RObject` looks just like a regular C++ move: + +```cpp +RObject obj1 = /* created on stack */; +RObject obj2 = std::move(obj1); +``` +**What happens here:** + +* The reflected type’s **move constructor** is invoked. +* Ownership of the object transfers into `obj2`. +* The moved-from object (`obj1`) becomes **empty**. +* No duplication or destruction happens — the object is simply relocated. + +👉 **Key idea:** +> *Stack move = reflected type’s move constructor is called.* + +### Moving Heap-Allocated Objects + +When you create an object reflectively with `alloc::Heap`, the instance is managed inside a **`std::unique_ptr`**. Moving such an `RObject` also uses standard C++ move semantics: + +```cpp +RObject obj1 = /* created on heap */; +RObject obj2 = std::move(obj1); +``` +**What happens here:** + +* The internal `unique_ptr` is moved. +* No move constructor of the reflected type is called. +* Ownership transfers to `obj2`. +* The moved-from object (`obj1`) becomes **empty**. +* The underlying heap object remains untouched and alive until its final owner is destroyed. + +👉 **Key idea** +> ***Heap move = `unique_ptr` move semantics (cheap pointer transfer).*** + +### Consistent Guarantees 🟨 + +Across both stack and heap moves: + +* The moved-from `RObject` is always **empty**. +* The destination `RObject` becomes the sole owner. +* RAII ensures proper cleanup — objects are destroyed once and only once. +* Cloning or invoking a moved-from object results in `rtl::error::EmptyRObject`. + +✅ Bottom Line +> ***“When you move an `RObject`, RTL either calls your type’s move constructor (stack) or transfers ownership of its `unique_ptr` (heap). In both cases, the source is emptied and ownership remains safe.”*** + +--- + +***More to come...*** diff --git a/text-design-docs/WHY_CPP_REFLECTION_MATTERS.md b/text-design-docs/WHY_CPP_REFLECTION_MATTERS.md new file mode 100644 index 00000000..58d7a1b6 --- /dev/null +++ b/text-design-docs/WHY_CPP_REFLECTION_MATTERS.md @@ -0,0 +1,133 @@ +# 🎯 Why Runtime Reflection in C++ (with RTL) Matters + +> **Position**: Runtime reflection is not “anti‑C++.” It’s an opt‑in capability that, when scoped and engineered correctly, unlocks workflows that are painful or impossible with templates alone—without betraying C++’s zero‑cost ethos. RTL makes this practical, safe, and tooling‑friendly. + +--- + +C++ culture favors compile‑time solutions, but not all problems are compile‑time problems. Static metaprogramming has costs too: binary/code size, compile times, and readability. + +RTL’s design (⚡ macro‑free, 🧩 external registration, ⏳ lazy/immutable `CxxMirror`, 🛠️ error‑code surfaces, 🔒 const‑by‑default, ♻️ deterministic lifetimes) reduces the classic risks of runtime reflection while preserving type safety where it matters. + +The philosophy is simple: use reflection at the edges (tooling, glue, scripting, plugins, serialization) and keep hot code paths static. + +--- + +## 🚧 Why Some C++ Developers Say “No” + +1. **Zero‑cost ideology** – Fear of paying for metadata you don’t use. +2. **Static‑first mindset** – Preference for templates/constexpr over any runtime mechanism. +3. **ABI/portability concerns** – Lack of a stable C++ ABI across platforms/compilers. +4. **Safety/predictability worries** – Fear of “stringly‑typed” APIs, hidden costs, harder debugging. +5. **Cultural inertia** – The ecosystem grew up without runtime reflection. + +These instincts are valid—but not disqualifiers. Instead, they set requirements for a responsible design. + +--- + +## ✨ RTL’s Philosophical Response + +* **Opt‑in, pay‑as‑you‑go** – Metadata is externally defined and lazy‑loaded via an immutable `CxxMirror`. If you don’t access reflection, you don’t pay. +* **No hidden global state** – No static registries, macros, or preprocessor hacks. Developers control what’s exposed and when. +* **Type‑safety discipline** – + + * 🚫 Exception‑free surfaces (errors via codes). + * 🔒 Const‑by‑default to avoid accidental mutation. + * 🎯 Conservative parameter matching (safe widenings, string‑like conversions, smart‑pointer transparencies) with clear rules. +* **Deterministic lifetimes** – `RObject` is a type‑erased, lifetime‑aware handle. It preserves `Heap`/`Stack` ownership and never hides deep copies. +* **Tooling‑friendly split** – Metadata providers and runtime consumers are decoupled; the mirror is swappable per build/mode and load‑on‑touch. + +📌 **Bottom line:** RTL preserves the values of C++ (control, performance, explicitness) while offering runtime shape where it’s needed. + +--- + +## 🚀 What Becomes Possible (Parity With Java/C#‑style Workflows) + +1. **📦 Generic Serialization/Deserialization** – Walk members/methods at runtime to build serializers without hand‑rolled boilerplate. +2. **🐍 Scripting Bridges (Lua/Python/JS)** – Expose app objects dynamically to scripts; invoke methods by name with safe conversions. +3. **🖼️ Inspector UIs & Editors** – Auto‑generate property panels (Qt/ImGui) from metadata; bind widgets to fields. +4. **🔌 Plugin & Module Systems** – Load `.so`/`.dll`, query its `CxxMirror`, discover callable endpoints. +5. **🧪 Test Discovery & Orchestration** – Enumerate test functions by convention at runtime—no macro registries. +6. **📡 RPC/IPC & Data Pipelines** – Reflective marshalling, schema introspection, versioned message handling. +7. **⚙️ Live Tooling/Automation** – Logging, telemetry, app consoles, REPLs, hot‑reloadable metadata providers. + +💡 These are exactly why ecosystems like Java/C# leaned on reflection—and with RTL, C++ can enjoy the same benefits while keeping hot paths static and optimized. + +--- + +## 📝 Minimal, Concrete Patterns With RTL + +**Reflective Call (method invoke)** + +```c++ +const rtl::CxxMirror& m = MyReflection(); + +auto cls = m.record("engine::Audio"); +auto [err, inst] = cls->create(/* args */); // heap or stack as requested +auto setVolume = cls->getMethod("setVolume"); +auto [err, vol] = setVolume->bind(inst).call(0.75); // conservative conversions apply +``` + +**Serializer Sketch (pseudo‑code)** + +```c++ +json to_json(const rtl::RObject& obj) { + auto t = obj.record(); + json j; + for (auto& field : t.fields()) { // planned field/property reflection + j[field.name()] = to_json(obj.get(field)); + } + return j; +} +``` + +**Plugin Mirror Boundary** + +```c++ +extern "C" const rtl::CxxMirror& PluginReflection(); +// Host loads plugin, inspects its mirror, and queries callable endpoints. +``` + +--- + +## 🛡️ Performance & Safety Guardrails + +* Keep reflection at the boundaries: UI, scripting, serialization, plugins. +* Cache lookups: Resolve handles once, reuse them. +* Avoid string dispatch in hot loops. +* Prefer `rtl::view` for const refs instead of materializing copies. +* Benchmark reflective sections separately. +* Prototype with reflection → specialize hotspots with templates later. + +--- + +## ❓ Addressing Common Objections + +**“Zero‑cost means no runtime reflection.”** + +> Zero‑cost means no *mandatory* cost. With RTL’s lazy mirror and external registration, unused metadata is never touched. + +**“Just use templates.”** + +> Templates can’t solve runtime shape problems (dynamic plugins, scripts, external schemas). Reflection shifts cost only where runtime shape is unavoidable. + +**“Reflection is unsafe and stringly‑typed.”** + +> RTL APIs are explicit and exception‑free. Conversions are conservative, and lifetimes are deterministic. + +**“ABI will bite you.”** + +> RTL treats the mirror as the stable boundary. Metadata is authored explicitly—not guessed from compiler ABI. + +**“It will bloat my binary.”** + +> You register only what you expose. Metadata is lazy and link‑time selectable. You can strip it in production builds. + +**“What about fields/enums/inheritance?”** + +> They’re on the roadmap. Current function/constructor focus already unlocks major workflows; adoption can be incremental. + +--- + +## 🔚 Final Take + +*C++ can do runtime reflection responsibly. The choice is not “templates or chaos.” With RTL’s explicit, lazy, exception‑free design and deterministic lifetimes, you get the power of runtime shape when you want it, and zero cost when you don’t. That is the C++ way.* diff --git a/text-sailors-log/DLLs-ThinkingOutLoud.md b/text-sailors-log/DLLs-ThinkingOutLoud.md new file mode 100644 index 00000000..d48bf8be --- /dev/null +++ b/text-sailors-log/DLLs-ThinkingOutLoud.md @@ -0,0 +1,80 @@ +### ReflectionTemplateLibrary (RTL) — Design Log + +*Author: Neeraj Singh* +*Date: 2025-08-28* + +--- + +## Core Vision + +RTL is designed as a **pure C++ runtime reflection system** that eliminates the need for macros, boilerplate, or special compilers. Unlike traditional attempts that only cover type introspection, RTL enables **full type usage reflectively**: constructing, invoking methods, accessing properties, and cloning — all with type safety and zero overhead abstraction. + +--- + +## Key Revelations & Insights + +### 1. Beyond Handles — True Reflective DLL Communication + +Traditionally, DLLs expose a handful of opaque handles and C-like APIs. With RTL, however, we can: + +* Load a DLL dynamically. +* Obtain **type info, methods, and objects as handles**. +* Pass PODs and c-strings with ABI safety. +* Invoke methods reflectively, as if types were known at compile time. + +This blurs the line between host and DLL. The DLL no longer feels foreign — **all its types are accessible via reflection**. Programmers can work with it using pure C++ syntax, without worrying about handles or memory quirks. + +**Revelation:** RTL turns DLLs into **transparent C++ modules**, not just opaque binaries. + +--- + +### 2. Unit Testing Reinvented with RTL + +With RTL, unit tests no longer need macro-heavy frameworks (like gtest, Catch2, etc.). Instead: + +* Tests are written as **pure C++ classes**. +* A host application can load these classes **reflectively** from a DLL. +* The test runner can execute test cases dynamically without any registration macros. + +**Impact:** CI/CD pipelines can directly run reflected tests — a **JUnit-like experience for C++**. + +**Revelation:** Macro-based test frameworks exist only because **runtime reflection was missing** in C++. + +--- + +### 3. DLL Boundary Abstraction Layer + +While DLLs normally require manual handle and lifetime management, RTL can provide an **API layer** that: + +* Automates DLL loading/unloading. +* Manages lifetimes of reflected objects. +* Exposes the DLL’s internal types as if they were local to the project. + +**Effect:** The DLL boundary disappears — programmers interact reflectively in C++ as if everything were part of one codebase. + +--- + +### 4. Safe Widening & Relaxed Parameter Matching + +* RTL supports **safe widening conversions** for PODs (e.g., `int → double`, `const char* → std::string`). +* This makes reflected calls much more natural and robust across DLL boundaries. + +**Impact:** Function invocation feels seamless even when type signatures differ slightly. + +--- + +### 5. Industry Implications + +* **Macro-based frameworks** (testing, serialization, RPC) exist only due to missing reflection. +* **Plugin ecosystems** in C++ can now be designed with the same transparency as Java/.NET. +* **Adoption Resistance?** Some may resist due to fear of dynamic features in C++, but the **utility will outweigh resistance**. + +**Revelation:** RTL can become the **go-to pattern** for plugin/DLL design and reflective test execution. + +--- + +## Next Steps + +* Maintain a **DLL-specific design log** (chronological, PDF) alongside this Canvas. +* Expand CI/CD integration examples (CTest, GitHub Actions, etc.). +* Keep cross-referencing insights between the main log (Canvas) and the DLL log. diff --git a/text-sailors-log/cloning-semantic-quirks-with-wrappers.md b/text-sailors-log/cloning-semantic-quirks-with-wrappers.md new file mode 100644 index 00000000..cfabb144 --- /dev/null +++ b/text-sailors-log/cloning-semantic-quirks-with-wrappers.md @@ -0,0 +1,112 @@ +# RTL Design Evolution Log + +## Milestone: Cloning Semantics — Unified Control with `rtl::copy` + +**Date:** 2025-08-16 +**Author:** Neeraj Singh + +--- + +### Problem Context + +Cloning objects inside RTL (`RObject`) originally had semantics tied only to value-copying. While this worked for most user-defined types, ambiguity and inconsistency emerged when dealing with **STL wrappers** such as `std::shared_ptr`, `std::unique_ptr`, and `std::optional`: + +* Should cloning a wrapper clone the *wrapper itself* (shallow copy)? +* Or should it clone the *underlying contained type* (deep copy)? +* What happens when the contained type is **non-copyable**? +* How do we make this intuitive yet efficient for real-world use? + +Performance concerns also came into play: deep-copying wrappers like `shared_ptr` in performance-critical applications would be wasteful when shallow-copy semantics (ref-count increments) are usually what developers expect. + +### Early Exploration + +1. **Default to Value Copy:** + + * Simple and safe. + * But performance-heavy when working with smart pointers. + * Non-intuitive: developers returning/consuming wrappers usually expect to work with the wrapper itself. + +2. **Default to Wrapper Copy:** + + * Better aligned with performance and developer expectations for `shared_ptr` and `optional`. + * But breaks intuition for cases where deep-copy of the underlying type was actually intended. + +Neither extreme fully captured the range of C++-native expectations. + +### Final Design + +To reconcile the competing needs, a **two-axis control** was introduced: + +```cpp +enum class alloc { Heap, Stack }; +enum class copy { Auto, Value, Wrapper }; + +template +std::pair clone() const; +``` + +#### Modes of Operation + +* **`copy::Value`** → Force deep copy of the contained entity `T`. + + * Errors out with `TypeNotCopyConstructible` if `T`’s copy ctor is deleted. + * Heap/stack behavior respected via `alloc`. + +* **`copy::Wrapper`** → Copy the wrapper itself. + + * `std::shared_ptr` shallow-copy increments ref-count. + * `std::unique_ptr` is forbidden (`TypeNotCopyConstructible`). + * Heap allocation of wrapper is disallowed (`StlWrapperHeapAllocForbidden`). + +* **`copy::Auto`** → Context-sensitive default: + + * If object was **RTL-allocated** (heap objects wrapped in `unique_ptr` internally): treat wrapper as transparent → **Value copy**. + * If object is a **non-RTL wrapper** (e.g., obtained from user return value): treat wrapper as significant → **Wrapper copy**. + +This provides an API that is **intuitive for developers**, while giving them control when they need it. + +However, RTL never performs deep copies internally during normal operations. All internal access uses zero-cost, read-only views by reference. + +--- + +### Error Handling Philosophy + +* **Fail Fast, No UB**: every illegal operation returns a clear `rtl::error`. + + * `error::EmptyRObject` if attempting to clone an empty `RObject`. + * `error::NotWrapperType` if `Wrapper` mode is requested on non-wrapper. + * `error::StlWrapperHeapAllocForbidden` if heap clone of wrapper is attempted. + * `error::TypeNotCopyConstructible` if underlying entity is not copyable. +* No silent fallbacks — clarity is always preferred. + +### Benefits + +* **Performance-Aware Defaults**: `Auto` intelligently chooses between shallow and deep copy based on context. +* **C++-Native Intuition**: Mirrors how developers think about copying raw types vs. wrappers in day-to-day C++. +* **Explicit Overrides**: Power users can explicitly request deep or shallow copy. +* **Consistency & Safety**: No hidden behavior, all outcomes expressed via `rtl::error`. + +### Example Use + +```cpp +// Smart default: deep-copy value if RTL-allocated, else shallow-copy wrapper +auto [err0, copy0] = robj.clone(); + +// Explicit deep-copy +auto [err1, copy1] = robj.clone(); + +// Explicit shallow-copy of shared_ptr wrapper +auto [err2, copy2] = robj.clone(); +``` + +--- + +### Why This Matters + +Reflection systems in C++ live at the intersection of **type safety**, **performance**, and **developer intuition**. By separating `alloc` and `copy` dimensions: + +* RTL ensures **safe lifetime management**. +* Developers get **precise control** without boilerplate. +* The library avoids performance pitfalls while staying faithful to native C++ semantics. + +This design resolves one of the most subtle challenges in runtime reflection — making **wrapper types transparent when desired, but still first-class citizens when needed**. diff --git a/text-sailors-log/cloning-semantics-at-a-glance.md b/text-sailors-log/cloning-semantics-at-a-glance.md new file mode 100644 index 00000000..e9344bd0 --- /dev/null +++ b/text-sailors-log/cloning-semantics-at-a-glance.md @@ -0,0 +1,104 @@ +# 🔄 Cloning Semantics — At a Glance + +Cloning in RTL is explicit and predictable. +When you call: + +```cpp +auto [err, copyObj] = robj.clone(); +``` + +you control **where** the clone is created (`alloc::Heap` vs `alloc::Stack`) and **what** is cloned (`copy::Value` vs `copy::Wrapper` vs `copy::Auto`). + +--- + +## 📌 The `copy` modes + +### `copy::Value` + +Deep copy of the underlying *contained type `T`*. +Wrappers are treated as transparent. + +✅ Examples: + +* `RObject` of `std::shared_ptr` → copy of `int`. +* `RObject` of `std::unique_ptr` → copy of `MyType` (if copy-constructible). + +❌ Errors if: + +* Contained type is not copy-constructible. +* Wrapper forbids value copy (e.g. `unique_ptr` with deleted copy ctor). + +--- + +### `copy::Wrapper` + +Copy the wrapper itself (shallow copy semantics). +Contained entity is *not* copied. + +✅ Examples: + +* `RObject` of `std::shared_ptr` → shallow copy (`use_count` increases). +* `RObject` of `std::optional` → copy of the optional wrapper. + +❌ Errors if: + +* Wrapper is not copyable (e.g. `unique_ptr`). +* Heap allocation of wrapper is disallowed (`StlWrapperHeapAllocForbidden`). + +--- + +### `copy::Auto` *(Default)* + +RTL decides based on context: + +* If object came from **RTL-managed heap allocation** (`unique_ptr` wrapping a constructed type) → deep-copy `Value`. +* If object came from an **external wrapper** (e.g. return value `shared_ptr`) → shallow-copy `Wrapper`. + +✅ Intuitive: +Behaves like “do what a C++ dev would expect here.” +You get value-copies for RTL-created objects, wrapper-copies for externally-supplied smart pointers. + +⚠️ Important Clarification +When an object originates from an RTL-managed heap allocation (internally wrapped in std::unique_ptr), the default copy::Auto resolves to Value semantics. + +* However, RTL never performs deep copies internally during normal operations. All internal access uses zero-cost, read-only views by reference. +* A deep copy of the contained type only occurs if the user explicitly requests it via clone(). + +This ensures maximum efficiency while keeping semantics intuitive. + +--- + +## 📌 The `alloc` modes + +* **`alloc::Stack`** → new clone lives on the stack. +* **`alloc::Heap`** → new clone lives on the heap. +* Heap + Wrapper is forbidden → `error::StlWrapperHeapAllocForbidden`. + +--- + +## ⚡ Examples + +```cpp +RObject robj = reflect(std::make_shared(42)); + +// Deep copy the int (wrapper is transparent). +auto [e0, vCopy] = robj.clone(); + +// Shallow copy the shared_ptr (reference-counted). +auto [e1, wCopy] = robj.clone(); + +// Let RTL decide: shared_ptr → wrapper copy. +auto [e2, autoCopy] = robj.clone(); +``` + +--- + +## 🧭 Quick Rules of Thumb + +* Want the **thing inside**? → `copy::Value` +* Want the **wrapper itself**? → `copy::Wrapper` +* Don’t want to think about it? → `copy::Auto` + +--- + +👉 With this, RTL cloning semantics mirror **how you’d naturally treat smart pointers and wrappers in C++** — no surprises, no magic, just transparent, explicit control. diff --git a/text-sailors-log/const-by-default-semantics.md b/text-sailors-log/const-by-default-semantics.md new file mode 100644 index 00000000..679a0995 --- /dev/null +++ b/text-sailors-log/const-by-default-semantics.md @@ -0,0 +1,142 @@ +# Design Log: Const Semantics in RTL + +**Author:** Neeraj Singh +**Date:** 2025-08-24 + +--- + +## Overview + +This document details the **const semantics** in RTL (ReflectionTemplateLibrary-CPP), covering the distinction between RTL-created (logically-const) objects and externally provided (true-const) objects. It also explains how method binding, overload resolution, and error handling are governed by this model. + +--- + +## Core Principles + +1. **Const-by-Default Discipline** + + * All objects created via RTL reflection are treated as immutable by default. + * External objects passed to RTL retain their declared constness without alteration. + +2. **Two Kinds of Constness** + + * **Logically-Const (RTL-Created):** Objects appear immutable but may be safely cast internally by RTL. + * **True-Const (External):** Objects declared const by user code; RTL strictly respects their constness and never applies const\_cast internally. + +--- + +## Method Binding & Overload Resolution + +### 1. RTL-Created Objects (Logically-Const) + +* **Default:** RTL always prefers binding to the `const` overload of a method. +* **Fallback:** If no `const` overload exists but a non-const overload is present, RTL applies a *safe logical const\_cast* and binds to the non-const overload. +* **Explicit Choice:** If both overloads exist, the user may explicitly select which to bind by using `rtl::constCast()`. + +### 2. Externally Provided Objects (True-Const) + +* **Allowed Binding:** RTL binds only to `const` overloads. +* **Illegal Binding Attempt:** If the user explicitly attempts to bind a true-const object to a non-const overload, RTL reports: + + * `rtl::error::IllegalConstCast`. +* **Missing Const Overload:** If only a non-const overload exists (no `const` variant), RTL reports: + + * `rtl::error::ConstOverloadMissing`. +* **Guarantee:** RTL never internally const\_casts true-const objects. + +--- + +## Error Semantics + +* `rtl::error::IllegalConstCast` → Raised when the user attempts to bind a true-const object to a non-const method. +* `rtl::error::ConstOverloadMissing` → Raised when a true-const object attempts a call but no `const` overload is available. + +--- + +### 📌 Const Overload Handling: Native C++ vs RTL + +**Objective:** Verify whether RTL’s fallback and overload resolution for const/non-const methods aligns with native C++ semantics. + +--- + +#### 🟦 Native C++ Const Method Resolution + +* **Case 1: Call on `const T` / `const T&` / `const T*`:** + + * Only `const` member functions are viable. + * If only a non-const overload exists → **compile-time error**. + * User may `const_cast` and call non-const, but that’s UB if the object is truly const. + +* **Case 2: Call on non-const `T` / `T&` / `T*`:** + + * If both const and non-const overloads exist → compiler **chooses the non-const overload**. + + * Reason: binding to a non-cv-qualified `this` is a better match than converting to `const this`. + * If only the const overload exists → call is **valid**. A non-const object **can call a const member**. + +--- + +#### 🟦 RTL Const Method Resolution + +* **True-const objects (external const instances):** + + * RTL mirrors native C++ strictly. + * Only const overloads are considered. + * If missing → returns **`rtl::error::ConstOverloadMissing`**. + * If user explicitly attempts a constCast → **`rtl::error::IllegalConstCast`**. + +* **Logically-const objects (RTL-created, safe for const\_cast):** + + * RTL defaults to **binding the const overload** even if both exist → defensive by design. + * User can explicitly call the non-const overload via **`rtl::constCast()`** when intended. + * If only non-const exists, RTL safely **falls back to non-const** (guaranteed safe, since RTL owns the object). + +--- + +#### ✅ Cross-Verification Result + +* With a **non-const object**, native C++ picks non-const overload if available, otherwise falls back to const overload. +* With a **const object**, native C++ restricts to const overloads, otherwise errors. +* RTL matches this baseline but adds safety defaults for **logically-const** objects: + + * Defaults to const when both are present. + * Requires explicit opt-in (`rtl::constCast()`) for non-const. + * Provides error signaling instead of UB for illegal casts. + +--- + +📖 **Conclusion:** +RTL’s fallback is fully aligned with native C++ semantics for true constness, while extending the model with explicit safety rules and clearer error channels for logically-const cases. This preserves C++ familiarity while adding reflection-layer discipline. + + +## Strengths of the Model + +* **Safety First:** Prevents unsafe const-casting on external objects. +* **Flexibility:** Allows controlled safe relaxation of constness on RTL-managed objects. +* **Clarity:** Overload resolution is predictable and explicit. +* **Alignment with C++:** Fully consistent with const-correctness principles. + +--- + +## Considerations + +* **Documentation Needs:** Users must clearly understand the distinction between logically-const and true-const. +* **Testing:** Edge cases (e.g., methods with only non-const overloads) must be thoroughly verified. +* **Consistency:** Const semantics must remain uniform across cloning, moving, and nested reflection contexts. + +--- + +## Comparative Note + +* **Java Reflection:** Does not enforce constness; all members can be accessed/mutated freely. +* **C# Reflection:** Similar to Java, though `readonly` exists at field level; reflection can still override it. +* **RTL:** Unique in enforcing const-by-default while distinguishing between safe logical const-casts (for RTL-owned objects) and strict const adherence (for external objects). + +--- + +## Summary + +The const semantics in RTL establish a principled model: + +* **RTL-created objects:** const by default, but safe to relax when needed; users may explicitly call non-const overloads with `rtl::constCast()`. +* **External objects:** const is strictly enforced; unsafe relaxation is disallowed. diff --git a/text-sailors-log/const-semantic-dialogues.md b/text-sailors-log/const-semantic-dialogues.md new file mode 100644 index 00000000..3800dfcd --- /dev/null +++ b/text-sailors-log/const-semantic-dialogues.md @@ -0,0 +1,78 @@ +### 🗨️ Dev Conversation (Revised): Evaluating RTL’s Handling of C++ Const‑Method Quirks + +**Updated:** 2025-08-25 + +> Two engineers are evaluating RTL from the outside (they don’t own it). They’re cross‑checking RTL’s behavior against **native C++ const overload rules** and reconciling any gotchas. + +--- + +#### Quick refresher: Native C++ rules for `const` vs `non‑const` member overloads + +1. **Call on a `const` object** + + * Overload resolution prefers **`const`‑qualified** member functions. + * If **no `const` overload exists**, a direct call to a non‑const member is a **compile‑time error**. + * A user may write `const_cast(obj).nonConst()`; the call compiles, **but any mutation of an originally `const` object yields undefined behavior**. + +2. **Call on a non‑const object** + + * If both overloads exist, overload resolution prefers the **non‑const** overload. + * If **only a `const` overload** exists, **calling it is perfectly valid**; there is **no compile error**. (A non‑const object can call a `const` member function.) + +These are the ground truths we’ll map RTL onto. + +--- + +#### Conversation + +**Dev A:** I’m reading RTL’s docs about const semantics. They talk about “logically‑const” for RTL‑created objects and “true‑const” for external ones. Before we judge that, let’s sanity‑check against native C++ rules we just reviewed. + +**Dev B:** Right. In native C++, a `const` object can only call `const` members unless you `const_cast`—and mutating that object is UB. A non‑const object prefers the non‑const overload, but it can still call a `const` member if that’s the only one available. + +**Dev A:** Cool. Now, how does RTL line up? + +**Dev B:** From what I gather: + +* **True‑const objects (externally provided into RTL):** + RTL treats them like native C++ `const` objects. It **only considers `const` overloads**. + • If the `const` overload is **missing**, RTL returns **`rtl::error::ConstOverloadMissing`** instead of trying anything clever. + • If the user **explicitly** tries to bind to a non‑const overload using `rtl::constCast()`, RTL rejects it with **`rtl::error::IllegalConstCast`**. + • RTL never performs an internal `const_cast` on such objects. + +* **Logically‑const objects (created by RTL via reflection):** + RTL defaults to the `const` overload to keep things safe and predictable. + • If **both** overloads exist, users can **explicitly opt into** the non‑const one using **`rtl::constCast()`**. + • If the **`const` overload is missing** but a **non‑const** one exists, RTL may **safely fall back** to the non‑const overload by performing an internal, guaranteed‑safe logical `const_cast` (since RTL owns the object’s lifecycle). + • This mirrors the “you could `const_cast` in C++ if it’s safe,” but **with stronger guarantees** because RTL distinguishes logical vs true constness. + +**Dev A:** So we should tweak the summary line to be precise: *“If the reflected object is const, RTL only considers the const overload. If it doesn’t exist, you’ll get `ConstOverloadMissing`.”* → That’s **only** for **true‑const** objects, right? + +**Dev B:** Exactly. For **true‑const**, missing `const` → `ConstOverloadMissing`. But for **logically‑const** RTL‑created objects, **missing `const` + present non‑const** → RTL will legitimately call the non‑const overload (safe internal `const_cast`). + +**Dev A:** And on the non‑const side, does RTL emulate native C++’s preference for the non‑const overload when both exist? + +**Dev B:** Conceptually yes—but with RTL’s **const‑by‑default discipline**, calls start from a logically‑const stance. So by default, RTL picks the `const` overload; **users must be explicit** (via `rtl::constCast()`) to select the non‑const overload. That’s a deliberate design: *safety first, explicit mutation second.* + +**Dev A:** That’s fair. It’s stricter than C++’s default, but predictable. And it still lets you reach the non‑const path when you mean it. + +**Dev B:** Right. And the error taxonomy makes intent obvious: + +* `rtl::error::ConstOverloadMissing` → true‑const object, only non‑const overload exists. +* `rtl::error::IllegalConstCast` → explicit attempt to force a non‑const call on a true‑const object. +* No error for RTL‑created objects when falling back to a non‑const overload—the fallback is **by design** and **safe**. + +**Dev A:** Last cross‑check with native C++: non‑const object calling a `const` member when that’s the only option—valid in C++. Does RTL allow the analogous scenario? + +**Dev B:** Yes—that maps to either (a) true‑const objects calling `const` members (the only option), or (b) logically‑const objects defaulting to `const` members even if the underlying instance is mutable. Both are consistent. + +**Dev A:** Makes sense. So the headline is: *RTL codifies C++’s const rules at runtime, adds provenance‑aware safety (true‑const vs logically‑const), defaults to const for clarity, and requires explicit opt‑in for mutation via `rtl::constCast()`.* + +**Dev B:** Exactly. Cleaner than ad‑hoc const\_casts in user code, and safer than pretending const doesn’t matter at runtime. + +--- + +#### TL;DR + +* Native C++: `const` object → only `const` overload (non‑const needs cast; mutating originally `const` is UB). Non‑const object → prefers non‑const; may call `const` if that’s all there is. +* RTL (true‑const): only `const` overload; missing `const` → `ConstOverloadMissing`; forcing non‑const → `IllegalConstCast`. +* RTL (logically‑const): defaults to `const`; missing `const` but non‑const present → safe fallback to non‑const; both present → user can explicitly pick non‑const with `rtl::constCast()`. diff --git a/text-sailors-log/copy-constructor-reflection.md b/text-sailors-log/copy-constructor-reflection.md new file mode 100644 index 00000000..bdfa04eb --- /dev/null +++ b/text-sailors-log/copy-constructor-reflection.md @@ -0,0 +1,52 @@ +# RTL Design Evolution Log + +## Entry: Restricting Direct Copy Constructor Calls in Reflection + +**Date:** 2025-08-16 +**Author:** Neeraj Singh + +### Problem Context + +In C++, copy constructors are universally available (unless explicitly deleted), but their **direct invocation is not semantically equivalent** to creating new objects via constructors. In most cases, if a programmer knows the type `T`, they would simply invoke `T()` or `T(other)` themselves. Reflection should not encourage redundant or confusing usage patterns. + +During testing with POD types like `char`, it became clear that exposing direct copy constructor calls through `Record::create<>()` added no value and introduced ambiguity: + +```cpp +optional charType = cxx::mirror().getRecord(reflected_id::char_t); +auto [err, rchar] = charType->create('Q'); +EXPECT_TRUE(err == rtl::error::SignatureMismatch); +``` + +The call above attempts to use the copy constructor signature for `char`. RTL rejects it with a `SignatureMismatch` error — **by design**. + +### Design Decision + +* **No direct copy constructor invocation via `Record::create<>()`.** + + * If the user knows the type `T`, they should construct it directly. + * Reflection only steps in when type erasure or runtime indirection is needed. + +* **Copy semantics are still fully supported, but in the right places:** + + * `RObject::clone<>()` uses the implicitly registered copy constructor safely. + * `rtl::reflect(T)` lets you reflect an existing object (copy constructed into `RObject`) when you want to treat it as type-erased. + +### Why This Matters + +This design avoids feature-bloat while preserving clarity: + +* Prevents **unintuitive misuse** of copy constructors through reflection. +* Ensures reflection remains a **tool for runtime type-erasure** and not a redundant duplication of normal C++ syntax. +* Keeps `Record::create<>()` semantically tied to *real object construction* (default or parameterized), not copying. +* Developers who need copying in a reflection context still have a direct and intuitive API: `clone()` or `reflect()`. + +### Benefits + +* **Clarity:** Users immediately understand what `create<>()` means — it *creates*, not *copies*. +* **Safety:** Avoids accidental misuse that could lead to semantic confusion. +* **Consistency:** Copying is consistently handled through `clone()` across all types. +* **Alignment with C++ Philosophy:** If you know the type, do it in plain C++; reflection is for when you don’t. + +--- + +✅ **Final Rule:** `Record::create<>()` never binds to copy constructor overloads. Copy semantics are available only through `RObject::clone()` or `rtl::reflect(T)`. diff --git a/text-sailors-log/design-summary-RObject.md b/text-sailors-log/design-summary-RObject.md new file mode 100644 index 00000000..f6239971 --- /dev/null +++ b/text-sailors-log/design-summary-RObject.md @@ -0,0 +1,62 @@ +RTL Design Evolution Log +Milestone: RObject – The Runtime Instance Container +Date: 2025-08-13 +Author: Neeraj Singh + +--- + +## Purpose + +`RObject` is the central runtime container in RTL — the bridge between compile-time registered metadata and actual object instances at runtime. It encapsulates: + +* The underlying value or pointer (type-erased in `std::any`). +* Ownership and lifetime management logic. +* Metadata (`RObjectId`) linking the instance to its reflection type info. +* Cloning and controlled movement. + +This design allows RTL to operate on objects without compile-time type knowledge while preserving safety, performance, and predictable semantics. + +--- + +## Core Characteristics + +* **Move-only:** No accidental copies; move constructor provided, move assignment deleted. +* **Explicit Cloning:** All duplication is via a stored `Cloner` function pointer; no hidden copies. +* **Type-Erased Storage:** `std::any` holds stack values, heap allocations, or wrapped move-only types like `std::unique_ptr`. +* **Consistent Metadata Link:** Every `RObject` carries an `RObjectId` to resolve runtime type operations. + +--- + +## Creation & Storage Rules + +* Built via type-trait-dispatched builders. +* Special handling for string-like types and raw C strings. +* Heap vs stack distinction recorded in metadata. +* Uses safe wrappers (e.g., `RObjectUptr`) for move-only types to ensure cross-compiler compatibility. +* RTL rule: **All `any_cast` operations retrieve values only as `const T&`** — preventing unwanted copies, even for copyable types. + +--- + +## Cloning & Lifetime Management + +* `Cloner` supports type-specific shallow or deep copies. +* Metadata preserves original allocation type and const-cast safety. +* Destructor access is validated — if deleted/private, heap allocation is rejected. +* No double-deletes or leaks across move and clone operations. + +--- + +## Interoperability & Conversion + +* Planned relaxed parameter type matching (e.g., `const char*` → `std::string`). +* Safe implicit conversions recognized. +* Works with values, refs, and pointers. + +--- + +## Benefits + +* **Predictable API:** Logical constness enforced; internal mutability for reflection only. +* **Cross-Compiler Consistency:** Single code path for MSVC, GCC, Clang. +* **Performance-Aware:** Avoids redundant copies and allocations. +* **Extensible:** Designed for future relaxed conversions, advanced lifetime rules, and complex type handling. diff --git a/text-sailors-log/design-summary-RObjectUPtr.md b/text-sailors-log/design-summary-RObjectUPtr.md new file mode 100644 index 00000000..a9c63b84 --- /dev/null +++ b/text-sailors-log/design-summary-RObjectUPtr.md @@ -0,0 +1,67 @@ +RTL Design Evolution Log +Milestone: RObjectUptr – Safe Ownership of Move-Only Types +Date: 2025-08-13 +Author: Neeraj Singh + +--- + +## Problem Context + +While integrating `std::unique_ptr` support into `RObject` for storing move-only types, an issue surfaced: + +* `std::any` requires the contained type to be CopyConstructible when using certain APIs or performing unintended copy operations. +* `std::unique_ptr` is move-only and cannot be copied, making it incompatible with `std::any` in some compilers (notably MSVC) at the construction step itself. + +### Key Goal + +Store move-only types (especially `std::unique_ptr`) inside `RObject` without relying on compiler-specific quirks and without triggering copy construction. + +--- + +## Initial Exploration + +### Direct `std::any` Storage + +* Works in GCC/Clang for some cases, but fails on MSVC during `std::any` construction. +* Behavior inconsistent across compilers — unacceptable for RTL’s cross-platform guarantee. + +### Conditional Compilation + +* Possible workaround, but introduces complexity and risk of platform divergence. +* Violates RTL’s design principle of consistent behavior across compilers. + +--- + +## Final Design + +A dedicated wrapper class — **RObjectUptr** — was introduced: + +### Key Features + +* Stores the move-only type directly in a safe, non-copyable wrapper. +* Enforces no accidental copy-construction within RTL internals. +* **All `std::any_cast` operations in RTL retrieve values only via `const T&`** — guaranteeing that no copies are ever made, even for copyable types. +* Allows `RObject` to hold and operate on move-only types transparently. +* No compiler-specific hacks; same code path for MSVC, GCC, and Clang. + +--- + +## Core Design Decisions + +1. **No Ownership Transfer in Copy Constructor** + The wrapper ensures no implicit transfer or move in copy contexts — this avoids subtle ownership bugs. + +2. **No `std::move` in Copy Constructor** + Now a simple, safe, “harmless-looking” class — easier to maintain and audit. + +3. **One Code Path for All Compilers** + Consistency is a feature, not an afterthought. + +--- + +## Benefits + +* **Predictable Behavior:** Developers using RTL know that move-only types will *just work* without compiler-specific surprises. +* **Maintainability:** No preprocessor conditionals to juggle. +* **Safety First:** Internals of RTL can’t accidentally cause lifetime errors. +* **Extensibility:** Paves the way for storing other move-only, non-copyable types. diff --git a/text-sailors-log/progress-timline.md b/text-sailors-log/progress-timline.md new file mode 100644 index 00000000..a5065a95 --- /dev/null +++ b/text-sailors-log/progress-timline.md @@ -0,0 +1,182 @@ +# RTL Milestone Timeline — RObject, `rtl::view`, and Cloning Semantics + +**Goal**: Build a non-intrusive, native-feeling runtime reflection system for C++ that preserves C++’s ownership, value categories, and performance expectations while enabling **relaxed parameter matching** across type-erased boundaries. + +--- + +## Phase 0 — Principles & North Star + +* **Non-intrusive**: No macros in user types. Registration is external and explicit. +* **C++-native intuition**: Reflected calls should behave like normal C++ calls (ownership, constness, overload resolution). +* **Zero accidental cost**: No hidden copies; pay only when you ask for it. +* **Deterministic lifetime**: Clear heap/stack semantics, explicit ownership. +* **Error-code based**: Robust, exception-free error paths. + +--- + +## Phase 1 — Why `RObject` exists + +**Problem**: After a reflected call, you often don’t know the static type of the result (or you want to pass it to another reflected call that expects a *different* but compatible type). + +**Solution**: `RObject` is a **type-erased, lifetime-aware runtime handle** that: + +* Stores the produced value (or pointer) and its metadata. +* Lets you **view** it as different target types (`view()`) when safe. +* Enables **relaxed parameter matching** by projecting the same stored entity into multiple compatible request shapes (e.g., `std::string` ↔ `const char*`, safe arithmetic widenings, etc.). + +**Design anchors**: + +* Internally uses `std::any` for storage (with strict rules, see Phase 2). +* Tracks allocation site and wrapper kind in `RObjectId` (heap/stack, wrapper type, const-cast safety). +* All internal access is via **read-only views by reference**; **no implicit cloning**. + +--- + +## Phase 2 — Storage & Ownership (type erasure without footguns) + +**Challenges** + +* `std::unique_ptr` cannot be copied; some toolchains reject placing it directly into `std::any`. +* `std::any` may trigger copy paths in naive designs. + +**Decisions** + +1. **Wrapper for move-only types**: Introduced `RObjectUPtr` — a simple, copyable-looking façade whose semantics are controlled by RTL. It enables consistent storage of `unique_ptr` across MSVC/GCC/Clang. +2. **`std::any` access rule**: **Every `any_cast` inside RTL is by `const T&` only**. Once constructed in-place, RTL never triggers a copy via `std::any` operations. +3. **Metadata (`RObjectId`)**: Records whether the stored entity is on heap or stack, whether it is a wrapper (`shared_ptr`, `unique_ptr`, etc.), and whether const-cast is safe (for RTL-managed instances only). + +**Outcomes** + +* Cross-compiler consistent behavior for move-only storage. +* No hidden copies; lifetime stays intuitive and explicit. + +--- + +## Phase 3 — `rtl::view` (see-through, zero-overhead access) + +**What a view is** + +* An immutable façade over the stored entity that **either** references the original value **or** materializes a value when a conversion is requested. +* Constructed with **bare types** only (`T`), then you decide how to consume it (ref/pointer/value) via `get()`. + +**Properties** + +* **Zero-copy until needed**: Viewing as the same type yields a const reference; viewing as different but compatible type *may* materialize a temporary (e.g., `std::string` from `const char*`). +* **No dangling**: The view’s lifetime is scoped to the `RObject` and call site. +* **Validation**: Views are only produced when statically/semantically safe; otherwise `std::optional` is empty. + +**Why it matters for relaxed matching** + +* A single stored value can be presented as `T`, `U`, or `V` if (and only if) conversions are safe by RTL policy. + +--- + +## Phase 4 — Smart Pointers: reflecting real-world ownership + +**Support added**: `std::unique_ptr`, `std::shared_ptr` + +**Semantics** + +* **`view>`**: + + * Multiple views can exist. Calling `get()` **moves** the pointer **once** out of the `RObject`-held storage; subsequent `get()` calls yield an empty `unique_ptr`. + * `RObject` itself remains valid after the move; only the contained `unique_ptr` becomes empty. +* **`view>`**: + + * `view->get()` as `const std::shared_ptr&` does **not** bump refcount. + * Acquiring by value creates a **shallow, ref-counted copy** (obvious from C++ intuition). +* **Transparent access to underlying `T`**: Users can request `view` directly; wrappers act as transparent containers whenever reasonable. + +**Safety rules** + +* RTL does **not** invent copying for noncopyable `T`. +* If the original object was **RTL-constructed on heap**, it is stored in an RTL-owned `unique_ptr` (transparent internally). Internal operations never deep-copy; cloning happens only on explicit request. + +--- + +## Phase 5 — Cloning Semantics (explicit, predictable) + +**Public API** + +```cpp +// Choose allocation site and clone target (Auto / Value / Wrapper) +template +std::pair RObject::clone() const; +``` + +**Copy target policy** + +* `copy::Value` → Deep-copy the **underlying value** `T` (if copy-constructible). Allocation site decided by `A`. +* `copy::Wrapper` → Copy the **wrapper** when applicable: + + * `shared_ptr` → shallow, ref-counted copy (allowed on `alloc::Stack`; **heap forbidden**). + * `unique_ptr` → not copyable → `error::TypeNotCopyConstructible`. +* `copy::Auto` → The intuitive default: + + * If the object is a **non-RTL** wrapper (e.g., a `shared_ptr` or a `unique_ptr` returned from user code), clone the **Wrapper**. + * If the object is **RTL-managed heap instance** (constructed via reflection, owned by RTL’s internal `unique_ptr`), clone the **Value** (deep-copy) *only when explicitly requested by the user via `clone()`*. + +**Allocation policy** + +* `alloc::Stack` → produces a stack-held instance when cloning `Value`, or a stack-held wrapper when cloning `Wrapper` (for `shared_ptr`). +* `alloc::Heap` → deep-copy to heap for `Value`; **forbid** heap-alloc of wrapper clones (`error::StlWrapperHeapAllocForbidden`). + +**Errors you’ll see** + +* `error::EmptyRObject`, `error::NotWrapperType`, `error::TypeNotCopyConstructible`, `error::StlWrapperHeapAllocForbidden`. + +**Crucial nuance** + +* *Efficiency preserved*: Despite the rule “If RTL-managed heap ⇒ deep-copy Value,” **RTL never performs hidden clones**. Internally, RTL only uses read-only references; cloning happens **solely** when the user calls `clone()`. + +**Evolution note** + +* Early iterations used an internal `EntityKind` (Value/Wrapper). Public API is now unified as `rtl::copy { Auto, Value, Wrapper }` for clarity. + +--- + +## Phase 6 — Relaxed Parameter Matching (roadmap & current policy) + +**Motivation**: Let users compose reflected calls without hand-writing glue types. + +**Current safe projections** + +* **String-like**: `const char* → std::string` (materialize), `std::string_view ↔ std::string` (configurable, conservative by default). +* **Arithmetic**: only **proven-safe widenings** (e.g., `int → long long`, `float → double`). No narrowing; no int↔float unless explicitly allowed. Policy backed by a compile-time trait (conservative by design). +* **Pointer/view**: From `unique_ptr` / `shared_ptr` to `T` via `view` (transparent read-only access), honoring ownership rules. + +**Future** + +* Enums, properties, composite types, inheritance — each with explicit, conservative matching tables. + +--- + +## Phase 7 — Testing Milestones + +* **`unique_ptr` round-trip**: reflect, multiple views, single-move extraction, post-move stability of `RObject`. +* **`shared_ptr` sharing**: ref-count behavior via read-only vs by-value access; wrapper clone vs value clone behavior; mixed ownership lifetimes. +* **User-defined noncopyable types** (`Node`): verify wrapper-level shallow clones succeed while value-level clones fail with `TypeNotCopyConstructible`; resource counters prove correct destruction. +* **Constructor misuse guard**: Calling `Record::create` with a copy-ctor signature reports `SignatureMismatch` (design choice: copy construction is reserved for `RObject::clone`). + +--- + +## Phase 8 — Performance & Safety Posture + +* **No hidden work**: All internal access is via const refs; no implicit deep copies. +* **Explicit costs**: Cloning is explicit; conversions happen only when `view` asks for them. +* **Thread- & exception-safety**: Error codes over exceptions; atomic counters for diagnostics; clear ownership tracking. +* **Cross-compiler consistency**: `RObjectUPtr` and `const-ref any_cast` discipline keep MSVC/GCC/Clang aligned. + +--- + +## Phase 9 — What’s Next + +* **Relaxed matching tables** (documented, auditable) for string-like and arithmetic families. +* **Property/Enum/Composite/Inheritance** reflection with the same semantics discipline. +* **ABI boundary story** (plugins): keeping binary boundaries stable by constraining surface types to ABI-friendly shapes. + +--- + +## One-liner Summary + +**RObject** stores *what you have*; **`rtl::view`** gives you *what you need*; **clone semantics** make the cost *explicit and intuitive*. Together, they enable relaxed parameter matching that still *feels like C++.* diff --git a/text-sailors-log/rtl-bind-function-design-log.md b/text-sailors-log/rtl-bind-function-design-log.md new file mode 100644 index 00000000..6c220fb6 --- /dev/null +++ b/text-sailors-log/rtl-bind-function-design-log.md @@ -0,0 +1,104 @@ +# Design Log: bind() — API semantics, purpose and usage + +**Author:** Neeraj Singh +**Date:** 2025-08-25 + +--- + +## Overview + +`bind()` is the central invocation API in RTL that expresses two responsibilities in one call: + +1. **Target binding** — which runtime object (if any) the method should be invoked on. +2. **Argument forwarding policy** — how call-site arguments are to be forwarded (value, lvalue-ref, rvalue-ref, const-ref). + +`bind()` therefore gives the user explicit control over *how* RTL should perceive the invocation context and *how* to forward arguments to resolve overloads correctly. This is essential in a C++ reflection system because value-category and constness affect overload selection and correctness. + +--- + +## API Surface (conceptual) + +```cpp +// Zero-argument form: no target, default forwarding +auto callSite = funcOrMethod->bind(); + +// Bind to an RObject target (by value) — used for instance methods +auto callSite = method->bind(RObject{...}); + +// Explicitly request treating the target as non-const +auto callSite = method->bind(rtl::constCast(RObject{...})); + +// Template form: specify forwarding categories for parameters +auto callSite = func->bind(); // e.g. bind() + +// Combination: bind the target and specify forwarding +auto callSite = method->bind(target); +``` +--- + +## Accepted argument types for `bind()` + +* `rtl::access::RObject` — binds the reflected object as the call target. This is the normal case for invoking instance methods. +* `rtl::constCast&&` (or equivalent explicit wrapper) — signals to RTL: "treat this RObject as non-const for this call". This is an explicit, user-driven request to relax logical constness; if the RObject is *true-const*, the runtime rejects the attempt (`rtl::error::IllegalConstCast`). + +`bind()` *only* accepts these target forms for instance binding. Passing other types as the target should be a compile-time error. + +--- + +## Template parameter pack (perfect-forwarding control) + +* `bind()` allows the caller to *explicitly declare the forwarding category* for each function parameter, e.g. `T`, `T&`, `T&&`, `const T&`. +* This is necessary because reflection cannot deduce value category from a runtime `any`-style container; when overload selection depends on rvalue vs lvalue, `bind<...>()` disambiguates the call. +* If omitted, RTL will attempt best-effort matching using available argument values; but some overloads (especially rvalue-ref overloads) require explicit `bind()` to select. + +--- + +## Semantics — how `bind()` interacts with RTL const model + +* If binding an **RObject created by RTL** (logically-const): + + * Default behavior: bind the target as logically-const (no mutation). Overload resolution will prefer `const` overloads. + * If `const` overload doesn't exist but a non-const overload does, RTL may internally perform a **safe logical `const_cast`** and call the non-const overload. + * If caller explicitly uses `rtl::constCast(RObject)` in `bind()`, RTL will select the non-const overload when safe. + +* If binding an **externally-provided true-const** RObject: + + * `bind()` preserves true constness. RTL will consider only `const` overloads. + * If user calls `bind(rtl::constCast(robj))`, RTL will reject with `rtl::error::IllegalConstCast`. + +* For **static functions**, the object-target passed to `bind()` is ignored (becomes a sink). However, `bind()` still controls perfect-forwarding for parameters. + +--- + +## Error behavior and diagnostics + +* `rtl::error::ConstCallViolation` — raised when a non-const method is attempted on a true-const object without constCast. +* `rtl::error::IllegalConstCast` — raised when user explicitly requests a constCast on a true-const object. +* `rtl::error::ConstOverloadMissing` — raised when a true-const object has no const overload to call. +* `rtl::error::SignatureMismatch` — raised when the forwarded argument types do not match any registered signature. + +Design note: `bind()` should validate as early as possible (during `bind()` return or immediately on `.call()` entry) and provide clear diagnostics. In debug builds provide helpful messages; in release builds keep errors lightweight. + +--- + +## Rationale and benefits + +* **Explicitness:** users can express intent (target identity, const-relaxation, forwarding categories) in a single, discoverable call. +* **Correctness:** avoids ambiguous runtime overload resolution; reduces accidental UB from naive const\_casts. +* **Ergonomics:** templates on `bind()` provide the minimal, explicit syntax needed for perfect-forwarding without changing the call-site semantics. +* **Consistency:** aligns with C++ overload and const rules while providing reflection-specific guarantees (logical vs true constness). + +--- + +## Recommended usage patterns + +* Use `bind()` with no template parameters for common cases where value-categories are natural (lvalue args, no rvalue-only overloads). +* Use `bind()` when you need to target rvalue-ref overloads. +* Use `bind(rtl::constCast(robj))` only when you intentionally want to invoke a non-const overload on an RTL-created object. +* Avoid binding an object to static methods in production code if you want stricter correctness. + +--- + +## Summary + +`bind()` is the explicit control point for method/function invocation in RTL. It unifies target binding and argument forwarding, enabling correct overload resolution in the face of C++'s value-category and const subtleties. The design balances strictness and ergonomics: conservative by default (favoring const), explicit when mutation or rvalue selection is intended. diff --git a/text-sailors-log/rtl-created-shared-ptr-design-exploration.md b/text-sailors-log/rtl-created-shared-ptr-design-exploration.md new file mode 100644 index 00000000..b26efc45 --- /dev/null +++ b/text-sailors-log/rtl-created-shared-ptr-design-exploration.md @@ -0,0 +1,60 @@ +RTL Design Evolution Log +Date: 2025-08-19 +Author: Neeraj Singh + +# Design Exploration Log — `rtl::alloc::Shared` + +## 🧩 The Motivation + +So far, RTL supports cloning semantics across **stack**, **heap**, and **wrapper-aware** modes. But one common C++ pattern isn’t fully reflected yet: **shared ownership.** + +In native C++, developers often pass around `std::shared_ptr` instead of deep-copying or moving values — especially when sharing is cheaper and semantically correct. RTL should preserve this intuition by offering a reflection-level equivalent. + +## ✨ The Proposal: `rtl::alloc::Shared` + +Introduce a new allocation selector: + +```c++ +enum class alloc { + Stack, + Heap, + Shared, // NEW: share ownership instead of cloning + // (Wrapper/Auto handled as part of copy semantics) +}; +``` + +## 🔧 Behavior by Case + +* **If the RObject wraps a `std::shared_ptr`** → produce a shallow copy (increment ref count). + +* **If the RObject wraps a `std::unique_ptr` owned by RTL** → promote to `std::shared_ptr` via `std::shared_ptr(std::move(unique_ptr))`. RTL keeps the semantics natural — a unique resource can be turned into shared ownership. + +* **If the RObject holds a stack/heap `T` (value)** → allocate a new `std::shared_ptr` holding that value, making it shareable across RTL boundaries. + +* **If the RObject holds a `std::weak_ptr`** → lock and wrap into `std::shared_ptr` if valid, or fail gracefully with `error::ExpiredWeakPtr`. + +## 🎯 Why This Matters + +* **Performance** – avoids deep copies when unnecessary, mirrors what devs already do manually with `shared_ptr`. +* **Intuition** – fits seamlessly with C++ semantics. You can choose: value-copy, heap-owning unique, or shareable pointer — exactly what you’d expect. +* **Flexibility** – lets RTL adapt to client code that’s designed around sharing semantics without forcing developers to write workarounds. + +## 📝 Example + +```c++ +RObject robj = reflect(std::make_unique()); + +// Instead of deep-copying MyType, just create a shared wrapper +auto [err, sharedObj] = robj.clone(); + +// sharedObj now reflects `std::shared_ptr` +EXPECT_TRUE(sharedObj.canViewAs>()); +``` + +## 🌱 Design Status + +This is an **exploration**, not finalized yet. The semantics look natural and consistent, but needs validation through: + +* Performance tests (to ensure shared vs deep-copy is beneficial in practice). +* API ergonomics (does `Shared` feel natural to users, or is it surprising?). +* Integration with `copy::Auto` (does auto-selection need to consider `Shared`?). diff --git a/text-sailors-log/smart-pointers-reflection-support.md b/text-sailors-log/smart-pointers-reflection-support.md new file mode 100644 index 00000000..282e03cd --- /dev/null +++ b/text-sailors-log/smart-pointers-reflection-support.md @@ -0,0 +1,97 @@ +# RTL Design Evolution Log + +## Milestone: Smart Pointer Reflection & Unwrap Semantics + +**Date:** 2025-08-15 +**Author:** Neeraj Singh + +--- + +### Problem Context + +While adding support for `std::unique_ptr`, `std::shared_ptr`, and `std::weak_ptr` to **RObject**, the challenge was to maintain: + +* Full reflection transparency. +* Correct ownership semantics. +* Cross-compiler compatibility. +* Intuitive behavior that matches native C++ usage. + +The goal: **Make working with reflected smart pointers feel as natural as working with them directly in C++.** + +--- + +### Key Challenges + +1. **Move-only types (`std::unique_ptr`)** + + * `std::any` requires `CopyConstructible` types in certain code paths. + * Direct storage of `unique_ptr` in `std::any` can fail on MSVC. + +2. **Shared ownership (`std::shared_ptr`)** + + * Reflection must preserve reference counts when passing between `RObject`s. + +3. **Weak references (`std::weak_ptr`)** + + * Reflection must allow locking to create temporary usable objects without accidental ownership transfer. + +4. **Developer intuition** + + * If you can do it in C++ normally, you should be able to do it via reflection — without unexpected surprises. + +--- + +### Final Design + +**1. Smart Pointer Storage** + +* All heap-created objects in RTL are wrapped in `std::unique_ptr` internally. +* The underlying type `T` can be accessed either as `std::unique_ptr` or directly as `T`. +* Deep-cloning from `unique_ptr` is the default when unwrapping. + +**2. `RObjectUptr` Wrapper** + +* Bypasses `std::any`'s copy-constructor requirement. +* Stores move-only types safely. +* Preserves cross-compiler behavior. + +**3. New Allocation Modes** + +* `alloc::Heap` – Normal heap allocation. +* `alloc::Stack` – Stack allocation. +* `alloc::UnwrapOnHeap` – Deep-clone underlying type `T` from inside smart pointer into a new heap object. +* `alloc::UnwrapOnStack` – Deep-clone underlying type `T` from inside smart pointer into a new stack object. + +**4. Transparent View Semantics** + +* If you know the type, you can view as `T` or `std::unique_ptr` directly. +* If you don't know the type, treating it as an opaque `RObject` is harmless — ownership rules are preserved. + +--- + +### Why This Matters + +In standard C++ development, handling smart pointers is muscle memory — you know when you can move, copy, or share. The **Unwrap-on-Heap/Stack** modes extend that instinct into reflection, allowing you to: + +* Safely clone from `unique_ptr` without stealing ownership. +* Share `shared_ptr` across reflected calls. +* Lock and use `weak_ptr` without surprises. + +The result: a **zero-friction reflection experience** where the developer’s mental model matches the runtime behavior. + +--- + +### Benefits + +* **Predictable behavior** — matches native C++ semantics. +* **Cross-platform consistency** — identical behavior on MSVC, GCC, and Clang. +* **Intuitive API** — unwrapping and cloning are explicit and intention-revealing. +* **Safe by default** — no accidental ownership leaks or lifetime bugs. + +--- + +### Next Steps + +* Expand test coverage for all combinations of smart pointer types and allocation modes. +* Document real-world usage patterns for devs unfamiliar with smart pointer internals. +* Add diagnostics when unwrapping fails due to inaccessible copy constructors. diff --git a/text-sailors-log/thread-safety-revised.md b/text-sailors-log/thread-safety-revised.md new file mode 100644 index 00000000..f27f22ba --- /dev/null +++ b/text-sailors-log/thread-safety-revised.md @@ -0,0 +1,87 @@ +# 📓 RTL Design Log — `CxxMirror` Thread-Safety & Singleton Model + +**Date:** 2025-08-29 +**Author:** Neeraj Singh + +--- + +## Background + +`rtl::CxxMirror` is the **root container** of RTL, holding all registrations (records, functions, methods, constructors). +By design, it should: + +* Be a **singleton** (one shared mirror across the program). +* Be **thread-safe** (registration happens once, and the mirror becomes immutable). +* Avoid unnecessary **runtime overhead** (locks, contention, etc.). + +Earlier, RTL used **internal locking** to guard critical sections during registration and query. This made the system **idiot-proof** — safe even if a user misused the mirror. + +At the same time, C++ itself guarantees that **`static`**\*\* local initialization is thread-safe and atomic\*\* (since C++11). This means that if a user follows the recommended *singleton pattern* (placing the mirror in a `static` local), the compiler already enforces thread-safety. + +This raised the question: +➡️ *Should RTL still keep internal locks even when the compiler already provides thread-safety guarantees?* + +--- + +## Initial Options Considered + +### Option 1 — Always Keep Locks + +* ✅ Safe in all cases (idiot-proof). +* ✅ Protects against misuse (e.g., non-static mirrors created across threads). +* ❌ Adds small but non-zero runtime overhead (mutex acquire/release). +* ❌ Redundant in canonical usage (`static` local mirror). + +### Option 2 — Remove Locks in `Static` Mode + +* ✅ Zero runtime overhead. +* ✅ Leverages compiler’s thread-safety. +* ❌ Risk of misuse: a user could instantiate a `CxxMirror` as a local/automatic variable in multiple threads and break invariants. +* ❌ “God mode” is nice for experts, but dangerous in practice. + +### Option 3 — Configurable Policy (ThreadSafe vs Static) + +* ✅ Gives users control (`rtl::CxxMirror` vs `rtl::CxxMirror`). +* ✅ Defaults to safe mode. +* ❌ Opens a footgun: users might misuse `Static` in a non-static context. +* ❌ Adds cognitive load (users must pick policies). + +--- + +## Later Idea: Template Singleton with Universe Indices + +We then considered making `CxxMirror` a **templated, compiler-enforced singleton**: + +* `CxxMirror` is **always static + thread-safe** by compiler guarantees. +* Users create **independent reflective universes** by indexing with a template parameter (`<0>, <1>, <2>…`). + +### Benefits + +* ✅ Compiler-enforced singleton. +* ✅ Zero runtime overhead. +* ✅ Multiple isolated universes. + +### Downsides + +* ❌ Burden on developers to manage indices. +* ❌ No semantic meaning behind numbers (risk of collisions, confusion). +* ❌ Removes flexibility of explicit construction. + +--- + +## 📅 Update — 2025-08-31 (Decision Reversal) + +After deeper consideration, we are **reverting to the original design** with **internal locking** and explicit user-managed construction. + +### Reasons for Reversal + +1. **Flexibility matters** — Users should retain the ability to explicitly construct and manage `rtl::CxxMirror`, not be forced into a rigid template+static model. +2. **Idiot-proof safety is already achieved** — Internal locks guarantee correctness even if a mirror is instantiated incorrectly (e.g., non-static, multi-threaded scenarios). +3. **Programmer responsibility is acceptable** — programmers will naturally recognize the essence of the singleton design pattern and use it wisely (placing the mirror in a `static` local). The docs can emphasize this idiom without enforcing it at the type system level. +4. **Simplicity of usage** — No need to manage artificial template indices or worry about collisions. + +### Final Takeaway + +> **CxxMirror will remain internally thread-safe via locks, with flexibility for explicit construction.** +> The **singleton pattern** (via `static` local) remains the recommended best practice, but it is *advisory, not enforced*. +> This keeps RTL both **idiot-proof and flexible**, avoiding unnecessary limitations while ensuring correctness in all usage scenarios.