From a469ccc13def49253ebe3b42f020a338073c4c01 Mon Sep 17 00:00:00 2001 From: Morgan Caron Date: Fri, 23 Oct 2026 18:38:09 +0200 Subject: [PATCH] feat(functional): Functional placeholders --- modules/Functional/Functional.mpp | 1 + modules/Functional/Placeholders.mpp | 140 ++++++++++++++++++++++++++++ tests/Functional/Placeholders.mpp | 73 +++++++++++++++ tests/UnitTests.mpp | 1 + 4 files changed, 215 insertions(+) create mode 100644 modules/Functional/Placeholders.mpp create mode 100644 tests/Functional/Placeholders.mpp diff --git a/modules/Functional/Functional.mpp b/modules/Functional/Functional.mpp index d46d8a57..1d25b3c2 100644 --- a/modules/Functional/Functional.mpp +++ b/modules/Functional/Functional.mpp @@ -1,3 +1,4 @@ export module CppUtils.Functional; export import CppUtils.Functional.LambdaCalculus; +export import CppUtils.Functional.Placeholders; diff --git a/modules/Functional/Placeholders.mpp b/modules/Functional/Placeholders.mpp new file mode 100644 index 00000000..b9c43da7 --- /dev/null +++ b/modules/Functional/Placeholders.mpp @@ -0,0 +1,140 @@ +export module CppUtils.Functional.Placeholders; + +import std; + +export namespace CppUtils::Functional::Placeholders +{ + namespace Detail + { + template + inline constexpr bool isExpression = false; + + template + concept ExpressionType = isExpression>; + + template + struct Arg final + { + template + requires (Index < sizeof...(Types)) + static constexpr auto operator()(Types&&... arguments) noexcept -> decltype(auto) + { + return std::get(std::forward_as_tuple(std::forward(arguments)...)); + } + }; + + template + inline constexpr bool isExpression> = true; + + struct Args final + { + constexpr auto operator+() const noexcept + { + return [](Types&&... arguments) static noexcept(noexcept((std::forward(arguments) + ...))) + -> decltype((std::forward(arguments) + ...)) { + return (std::forward(arguments) + ...); + }; + } + }; + + constexpr auto evaluate(auto&& expression, auto&&... arguments) -> decltype(auto) + { + if constexpr (ExpressionType) + return std::forward(expression)(std::forward(arguments)...); + else + return std::forward(expression); + } + + template + struct CallOperation final + { + Callee callee; + std::tuple args; + + template + constexpr auto operator()(Types&&... runtimeArgs) const -> decltype(auto) + { + auto evalArg = [&](Arg&& arg) -> decltype(auto) { + return evaluate(std::forward(arg), runtimeArgs...); + }; + + return std::apply([&](auto&&... unpackedArgs) -> decltype(auto) { + return evaluate(callee, runtimeArgs...)( + evalArg(unpackedArgs)...); + }, args); + } + }; + + template + inline constexpr bool isExpression> = true; + + template + struct BinaryOperation final + { + Left left; + Right right; + + template + constexpr auto operator()(Types&&... arguments) const + noexcept(noexcept(Operation{}( + evaluate(left, std::forward(arguments)...), + evaluate(right, std::forward(arguments)...)))) + -> decltype(Operation{}( + evaluate(left, std::forward(arguments)...), + evaluate(right, std::forward(arguments)...))) + { + return Operation{}( + evaluate(left, std::forward(arguments)...), + evaluate(right, std::forward(arguments)...)); + } + }; + + template + inline constexpr bool isExpression> = true; + + template + requires (ExpressionType or ExpressionType) + constexpr auto operator*(Left&& left, Right&& right) + { + return BinaryOperation, std::remove_cvref_t, std::remove_cvref_t>{ + std::forward(left), std::forward(right)}; + } + + template + requires (ExpressionType or ExpressionType) + constexpr auto operator+(Left&& left, Right&& right) + { + return BinaryOperation, std::remove_cvref_t, std::remove_cvref_t>{ + std::forward(left), std::forward(right)}; + } + + template + requires (ExpressionType or ExpressionType) + constexpr auto operator%(Left&& left, Right&& right) + { + return BinaryOperation, std::remove_cvref_t, std::remove_cvref_t>{ + std::forward(left), std::forward(right)}; + } + + template + requires (ExpressionType or ExpressionType) + constexpr auto operator==(Left&& left, Right&& right) + { + return BinaryOperation, std::remove_cvref_t, std::remove_cvref_t>{ + std::forward(left), std::forward(right)}; + } + } + + inline constexpr Detail::Arg<0> _0; + inline constexpr Detail::Arg<1> _1; + inline constexpr Detail::Arg<2> _2; + inline constexpr Detail::Arg<3> _3; + inline constexpr Detail::Arg<4> _4; + inline constexpr Detail::Arg<5> _5; + inline constexpr Detail::Arg<6> _6; + inline constexpr Detail::Arg<7> _7; + inline constexpr Detail::Arg<8> _8; + inline constexpr Detail::Arg<9> _9; + + inline constexpr Detail::Args _args; +} diff --git a/tests/Functional/Placeholders.mpp b/tests/Functional/Placeholders.mpp new file mode 100644 index 00000000..b87586ad --- /dev/null +++ b/tests/Functional/Placeholders.mpp @@ -0,0 +1,73 @@ +export module CppUtils.UnitTests.Functional.Placeholders; + +import std; +import CppUtils; + +namespace CppUtils::UnitTest::Functional::Placeholders +{ + template + concept Addable = requires (Left left, Right right) { left + right; }; + + auto _ = testSuite("Functional/Placeholders") = [](TestSuite& suite) { + using namespace CppUtils::Functional::Placeholders; + + suite.test("Power of 2 with _0 * _0") = [&] { + constexpr auto powerOfTwo = _0 * _0; + expect(powerOfTwo(2)) == 4; + expect(powerOfTwo(5)) == 25; + }; + + suite.test("Multiplication with _0 * _1") = [&] { + constexpr auto multiplication = _0 * _1; + expect(multiplication(2, 3)) == 6; + expect(multiplication(7, 8)) == 56; + }; + + // https://fr.wikipedia.org/wiki/Multiplieur-accumulateur + suite.test("Fused multiply-add with _0 * _1 + _2") = [&] { + constexpr auto fusedMultiplyAdd = _0 * _1 + _2; + expect(fusedMultiplyAdd(1, 2, 3)) == 5; + expect(fusedMultiplyAdd(4, 5, 6)) == 26; + }; + + suite.test("Variadic sum with +_args") = [&] { + constexpr auto sum = +_args; + expect(sum(1, 2, 3)) == 6; + expect(sum(10, 20, 30, 40)) == 100; + }; + + suite.test("Range filtering with _0 % 2 == 0") = [&] { + const auto values = std::views::iota(0, 10) + | std::views::filter(_0 % 2 == 0) + | std::ranges::to(); + + expect(values) == std::vector{0, 2, 4, 6, 8}; + }; + + suite.test("Higher-order index selection up to _9") = [&] { + constexpr auto selectNinth = _8; + expect(selectNinth(10, 20, 30, 40, 50, 60, 70, 80, 90)) == 90; + + constexpr auto selectTenth = _9; + expect(selectTenth(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) == 10; + }; + + suite.test("Combination with literals") = [&] { + constexpr auto computation = (_0 + 10) * _1; + expect(computation(5, 2)) == 30; + }; + + suite.test("Literal on the left side") = [&] { + constexpr auto computation = 10 + _0; + expect(computation(5)) == 15; + }; + + suite.test<[] { + using CppUtils::Functional::Placeholders::Detail::Args; + return not Addable + and Addable + and Addable + and Addable; + }>("Operators ignore unrelated types"); + }; +} diff --git a/tests/UnitTests.mpp b/tests/UnitTests.mpp index fd00fca2..e97dcb9b 100644 --- a/tests/UnitTests.mpp +++ b/tests/UnitTests.mpp @@ -32,6 +32,7 @@ export import CppUtils.UnitTests.FileSystem.FileStaging; export import CppUtils.UnitTests.FileSystem.IndexedStorage; export import CppUtils.UnitTests.FileSystem.MeshPager; export import CppUtils.UnitTests.FileSystem.Watcher; +export import CppUtils.UnitTests.Functional.Placeholders; export import CppUtils.UnitTests.Language.ASTParser; export import CppUtils.UnitTests.Language.CLikeCompiler; export import CppUtils.UnitTests.Language.CSV.Mapping;