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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 116 additions & 8 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,85 @@ jobs:

# FIXME: We're having a linker error wrt. Catch2 on Windows with MSVC
windows-msvc:
name: "Windows - MSVC"
name: "Windows - MSVC - C++${{ matrix.cxx_standard }}"
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
cxx_standard: [20, 23]
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: choco install ninja
- name: Setup MSVC developer environment
uses: ilammy/msvc-dev-cmd@v1
- name: "vcpkg: Install dependencies"
uses: lukka/run-vcpkg@v11.1
id: runvcpkg
with:
vcpkgDirectory: ${{ runner.workspace }}/vcpkg
vcpkgGitCommitId: 80403036a665cb8fcc1a1b3e17593d20b03b2489
vcpkgGitCommitId: eb2d3a3279fd019cb7733072d86900d0ad2a1aef
# The preset names a specific Visual Studio generator; Ninja builds with whichever version the runner provides.
- name: configure
run: cmake --preset windows-cl-release -D REFLECTION_TESTING=ON
run: >
cmake --preset windows-cl-release
-G Ninja
-D CMAKE_BUILD_TYPE=Release
-D REFLECTION_TESTING=ON
-D CMAKE_CXX_STANDARD=${{ matrix.cxx_standard }}
env:
VCPKG_ROOT: "${{ runner.workspace }}/vcpkg"
- name: build
run: cmake --build --preset windows-cl-release
- name: test
run: ctest --preset windows-cl-release -VV

windows-clangcl:
name: "Windows - ClangCL - C++${{ matrix.cxx_standard }}"
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
cxx_standard: [20, 23]
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: choco install ninja
- name: Setup MSVC developer environment
uses: ilammy/msvc-dev-cmd@v1
- name: "vcpkg: Install dependencies"
uses: lukka/run-vcpkg@v11.1
id: runvcpkg
with:
vcpkgDirectory: ${{ runner.workspace }}/vcpkg
vcpkgGitCommitId: eb2d3a3279fd019cb7733072d86900d0ad2a1aef
- name: configure
run: >
cmake --preset windows-clangcl-release
-D REFLECTION_TESTING=ON
-D CMAKE_CXX_STANDARD=${{ matrix.cxx_standard }}
-D CMAKE_TOOLCHAIN_FILE="${{ runner.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake"
env:
VCPKG_ROOT: "${{ runner.workspace }}/vcpkg"
- name: build
run: cmake --build --preset windows-clangcl-release
- name: test
run: ctest --preset windows-clangcl-release -VV

ubuntu24-clang:
name: "Ubuntu Linux - Clang ${{ matrix.clang_version }}"
name: "Ubuntu Linux - Clang ${{ matrix.clang_version }} - C++${{ matrix.cxx_standard }}"
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
clang_version: [17, 18]
clang_version: [17, 18, 19, 20, 21, 22]
cxx_standard: [20, 23, 26]
exclude:
# Clang 17 cannot compile the C++23 <tuple> of the libstdc++ 14 found on Ubuntu 24.04
- clang_version: 17
cxx_standard: 23
- clang_version: 17
cxx_standard: 26
steps:
- uses: actions/checkout@v4
- name: Install clang
Expand All @@ -51,26 +102,83 @@ jobs:
- name: Install ninja and catch2
run: sudo apt-get install ninja-build catch2
- name: configure
run: cmake --preset linux-clang-release -D REFLECTION_TESTING=ON
run: >
cmake --preset linux-clang-release
-D REFLECTION_TESTING=ON
-D CMAKE_CXX_COMPILER=clang++-${{ matrix.clang_version }}
-D CMAKE_CXX_STANDARD=${{ matrix.cxx_standard }}
- name: build
run: cmake --build --preset linux-clang-release
- name: test
run: ctest --preset linux-clang-release -VV

ubuntu24-gcc:
name: "Ubuntu Linux - GCC ${{ matrix.gcc_version }}"
name: "Ubuntu Linux - GCC ${{ matrix.gcc_version }} - C++${{ matrix.cxx_standard }}"
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
gcc_version: [13, 14]
cxx_standard: [20, 23, 26]
exclude:
# CMake has no C++26 flag for GCC 13
- gcc_version: 13
cxx_standard: 26
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: sudo apt-get install ninja-build catch2 g++-${{ matrix.gcc_version }}
- name: configure
run: cmake --preset linux-gcc-release -D REFLECTION_TESTING=ON
run: >
cmake --preset linux-gcc-release
-D REFLECTION_TESTING=ON
-D CMAKE_CXX_COMPILER=g++-${{ matrix.gcc_version }}
-D CMAKE_CXX_STANDARD=${{ matrix.cxx_standard }}
- name: build
run: cmake --build --preset linux-gcc-release
- name: test
run: ctest --preset linux-gcc-release -VV

# GCC releases newer than what Ubuntu 24.04 packages, via the official GCC container images.
linux-gcc-container:
name: "Linux - GCC ${{ matrix.gcc_version }} - C++${{ matrix.cxx_standard }}"
runs-on: ubuntu-24.04
container: gcc:${{ matrix.gcc_version }}
strategy:
fail-fast: false
matrix:
gcc_version: [15, 16]
cxx_standard: [20, 23, 26]
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
apt-get update
apt-get install -y cmake ninja-build
- name: configure
run: >
cmake --preset linux-gcc-release
-D REFLECTION_TESTING=ON
-D CMAKE_CXX_STANDARD=${{ matrix.cxx_standard }}
- name: build
run: cmake --build --preset linux-gcc-release
- name: test
run: ctest --preset linux-gcc-release -VV

macos:
name: "macOS - AppleClang - C++${{ matrix.cxx_standard }}"
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
cxx_standard: [20, 23]
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: brew install ninja catch2
- name: configure
run: cmake --preset macos-release -D REFLECTION_TESTING=ON -D CMAKE_CXX_STANDARD=${{ matrix.cxx_standard }}
- name: build
run: cmake --build --preset macos-release
- name: test
run: ctest --preset macos-release -VV
4 changes: 3 additions & 1 deletion CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@
{ "name": "linux-gcc-debug", "configurePreset": "linux-gcc-debug", "output": {"outputOnFailure": true}, "execution": { "noTestsAction": "error", "stopOnFailure": true } },
{ "name": "linux-gcc-release", "configurePreset": "linux-gcc-release", "output": {"outputOnFailure": true}, "execution": { "noTestsAction": "error", "stopOnFailure": true } },
{ "name": "linux-clang-debug", "configurePreset": "linux-clang-debug", "output": {"outputOnFailure": true}, "execution": { "noTestsAction": "error", "stopOnFailure": true } },
{ "name": "linux-clang-release", "configurePreset": "linux-clang-release", "output": {"outputOnFailure": true}, "execution": { "noTestsAction": "error", "stopOnFailure": true } }
{ "name": "linux-clang-release", "configurePreset": "linux-clang-release", "output": {"outputOnFailure": true}, "execution": { "noTestsAction": "error", "stopOnFailure": true } },
{ "name": "macos-debug", "configurePreset": "macos-debug", "output": {"outputOnFailure": true}, "execution": { "noTestsAction": "error", "stopOnFailure": true } },
{ "name": "macos-release", "configurePreset": "macos-release", "output": {"outputOnFailure": true}, "execution": { "noTestsAction": "error", "stopOnFailure": true } }
]
}
91 changes: 81 additions & 10 deletions include/reflection-cpp/reflection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,26 +121,97 @@ namespace detail
// return std::source_location::current().function_name();
return REFLECTION_PRETTY_FUNCTION;
}
} // namespace detail

constexpr size_t MaxReflectionMemerCount = 150;

namespace detail
{
#if defined(__cpp_structured_bindings) && __cpp_structured_bindings >= 202411L
// A structured binding pack yields the member count directly, at constant compile-time cost.
//
// Clang provides structured binding packs as an extension before C++26, and warns about their use there.
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc++26-extensions"
#endif
template <class T>
auto CountMembersImpl(T& t)
{
[[maybe_unused]] auto& [... members] = t;
return std::integral_constant<size_t, sizeof...(members)> {};
}
#if defined(__clang__)
#pragma clang diagnostic pop
#endif

template <class AggregateType, class... Args>
requires(std::is_aggregate_v<AggregateType>)
constexpr inline auto CountMembers = []() constexpr {
template <class T>
constexpr inline size_t CountMembers = decltype(CountMembersImpl(std::declval<T&>()))::value;
#else
template <class T, size_t... I>
consteval bool AcceptsInitializersImpl(std::index_sequence<I...> /*unused*/)
{
// NOLINTNEXTLINE(modernize-use-designated-initializers)
if constexpr (requires { AggregateType { Args {}..., AnyType {} }; })
return CountMembers<AggregateType, Args..., AnyType>;
return requires { T { (static_cast<void>(I), AnyType {})... }; };
}

// Whether the aggregate T can be initialized from exactly N values.
//
// With N being the member count and F the position of the last member without a default constructor
// (0 if there is none), exactly the counts in [F, N] are accepted.
template <class T, size_t N>
constexpr inline bool AcceptsInitializers = AcceptsInitializersImpl<T>(std::make_index_sequence<N> {});

// Finds the smallest accepted initializer count, searching linearly from N.
template <class T, size_t N = 0>
consteval size_t FirstAcceptedInitializerCount()
{
if constexpr (AcceptsInitializers<T, N>)
return N;
else if constexpr (N < MaxReflectionMemerCount)
return FirstAcceptedInitializerCount<T, N + 1>();
else
{
static_assert(N < MaxReflectionMemerCount,
"Reflection: unable to count the members of this aggregate type. It either has more "
"than MaxReflectionMemerCount (150) members, or it has a member that cannot be "
"initialized from a single value (e.g. a reference member).");
return 0;
}
}

// Finds the largest accepted initializer count by binary search, given that Low is accepted and High is not.
template <class T, size_t Low, size_t High>
consteval size_t LastAcceptedInitializerCount()
{
if constexpr (High - Low == 1)
return Low;
else if constexpr (AcceptsInitializers<T, Low + ((High - Low) / 2)>)
return LastAcceptedInitializerCount<T, Low + ((High - Low) / 2), High>();
else
return LastAcceptedInitializerCount<T, Low, Low + ((High - Low) / 2)>();
}

// Given that Low is accepted, doubles Step until a rejected count is found, then bisects that range.
template <class T, size_t Low, size_t Step = 1>
consteval size_t CountMembersFrom()
{
if constexpr (AcceptsInitializers<T, Low + Step>)
return CountMembersFrom<T, Low + Step, Step * 2>();
else
return sizeof...(Args);
}();
return LastAcceptedInitializerCount<T, Low, Low + Step>();
}

template <class T>
constexpr inline size_t CountMembers = CountMembersFrom<T, FirstAcceptedInitializerCount<T>()>();
#endif
} // namespace detail

// Count the number of members in an aggregate type.
template <class T>
requires(std::is_aggregate_v<std::remove_cvref_t<T>>)
constexpr inline auto CountMembers = detail::CountMembers<std::remove_cvref_t<T>>;

constexpr size_t MaxReflectionMemerCount = 150;

/**

elisp functions to fill the ToTuple function
Expand Down Expand Up @@ -1273,7 +1344,7 @@ void CallOnMembers(Object& object, Callable&& callable)
template <typename Object, typename Callable>
void CallOnMembersWithoutName(Object& object, Callable&& callable)
{
EnumerateMembers(object, [&]<size_t I, typename T>(T&& value) { callable.template operator()<I, T>(value); });
EnumerateMembers(object, [&]<size_t I, typename T>(T&& value) { callable.template operator()<I, T>(value); });
}

/// Folds over the members of a type without an object of it.
Expand Down
58 changes: 55 additions & 3 deletions test-reflection-cpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ struct SingleValueRecord
int value;
};

struct NoDefaultCtor
{
NoDefaultCtor() = delete;
constexpr NoDefaultCtor(int) {}
};

struct WithReq
{
int a;
NoDefaultCtor b;
};

struct WithReq2
{
int a;
int _a;
NoDefaultCtor b;
};

TEST_CASE("MemberIndex", "[reflection]")
{
static_assert(Reflection::MemberIndexOf<&Person::name> == 0);
Expand Down Expand Up @@ -64,6 +83,41 @@ TEST_CASE("NameOf", "[reflection]")
CHECK(singleValueField == "value");
}

struct NoDefaultCtorFirst
{
NoDefaultCtor a;
int b;
};

struct NoDefaultCtorMiddle
{
int a;
NoDefaultCtor b;
int c;
std::string d;
};

struct NoDefaultCtorOnly
{
NoDefaultCtor a;
NoDefaultCtor b;
};

TEST_CASE("CountMembers.NoDefaultCtor", "[reflection]")
{
static_assert(Reflection::CountMembers<WithReq> == 2);
static_assert(Reflection::CountMembers<WithReq2> == 3);
static_assert(Reflection::CountMembers<NoDefaultCtorFirst> == 2);
static_assert(Reflection::CountMembers<NoDefaultCtorMiddle> == 4);
static_assert(Reflection::CountMembers<NoDefaultCtorOnly> == 2);

auto record = NoDefaultCtorMiddle { .a = 1, .b = NoDefaultCtor { 2 }, .c = 3, .d = "four" };
auto names = std::vector<std::string_view> {};
Reflection::EnumerateMembers(
record, [&]<size_t I, typename T>(T&&) { names.push_back(Reflection::MemberNameOf<I, NoDefaultCtorMiddle>); });
CHECK(names == std::vector<std::string_view> { "a", "b", "c", "d" });
}

TEST_CASE("single value record", "[reflection]")
{
static_assert(Reflection::CountMembers<SingleValueRecord> == 1);
Expand All @@ -78,7 +132,6 @@ TEST_CASE("single value record", "[reflection]")
CHECK(name == "value");
CHECK(value == 42);
});

}

TEST_CASE("core", "[reflection]")
Expand Down Expand Up @@ -170,7 +223,6 @@ TEST_CASE("EnumerateMembers.partial", "[reflection]")
});
}


TEST_CASE("CallOnMembers", "[reflection]")
{
auto ps = Person { .name = "John Doe", .email = "john@doe.com", .age = 42 };
Expand Down Expand Up @@ -292,6 +344,6 @@ TEST_CASE("Compare.nested", "[reflection]")
TEST_CASE("TemplateFor over sequence", "[refleciton]")
{
std::string result {};
Reflection::template_for<std::integer_sequence<size_t, 3, 2, 1>>([&]<size_t I>(){result += std::to_string(I);});
Reflection::template_for<std::integer_sequence<size_t, 3, 2, 1>>([&]<size_t I>() { result += std::to_string(I); });
CHECK(result == "321");
}
Loading
Loading