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
1 change: 1 addition & 0 deletions modules/Functional/Functional.mpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export module CppUtils.Functional;

export import CppUtils.Functional.LambdaCalculus;
export import CppUtils.Functional.Placeholders;
140 changes: 140 additions & 0 deletions modules/Functional/Placeholders.mpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
export module CppUtils.Functional.Placeholders;

import std;

export namespace CppUtils::Functional::Placeholders
{
namespace Detail
{
template<class T>
inline constexpr bool isExpression = false;

template<class T>
concept ExpressionType = isExpression<std::remove_cvref_t<T>>;

template<std::size_t Index>
struct Arg final
{
template<class... Types>
requires (Index < sizeof...(Types))
static constexpr auto operator()(Types&&... arguments) noexcept -> decltype(auto)
{
return std::get<Index>(std::forward_as_tuple(std::forward<Types>(arguments)...));
}
};

template<std::size_t Index>
inline constexpr bool isExpression<Arg<Index>> = true;

struct Args final
{
constexpr auto operator+() const noexcept
{
return []<class... Types>(Types&&... arguments) static noexcept(noexcept((std::forward<Types>(arguments) + ...)))
-> decltype((std::forward<Types>(arguments) + ...)) {
return (std::forward<Types>(arguments) + ...);
};
}
};

constexpr auto evaluate(auto&& expression, auto&&... arguments) -> decltype(auto)
{
if constexpr (ExpressionType<decltype(expression)>)
return std::forward<decltype(expression)>(expression)(std::forward<decltype(arguments)>(arguments)...);
else
return std::forward<decltype(expression)>(expression);
}

template<class Callee, class... Args>
struct CallOperation final
{
Callee callee;
std::tuple<Args...> args;

template<class... Types>
constexpr auto operator()(Types&&... runtimeArgs) const -> decltype(auto)
{
auto evalArg = [&]<class Arg>(Arg&& arg) -> decltype(auto) {
return evaluate(std::forward<Arg>(arg), runtimeArgs...);
};

return std::apply([&](auto&&... unpackedArgs) -> decltype(auto) {
return evaluate(callee, runtimeArgs...)(
evalArg(unpackedArgs)...);
}, args);
}
};

template<class Callee, class... Args>
inline constexpr bool isExpression<CallOperation<Callee, Args...>> = true;

template<class Operation, class Left, class Right>
struct BinaryOperation final
{
Left left;
Right right;

template<class... Types>
constexpr auto operator()(Types&&... arguments) const
noexcept(noexcept(Operation{}(
evaluate(left, std::forward<Types>(arguments)...),
evaluate(right, std::forward<Types>(arguments)...))))
-> decltype(Operation{}(
evaluate(left, std::forward<Types>(arguments)...),
evaluate(right, std::forward<Types>(arguments)...)))
{
return Operation{}(
evaluate(left, std::forward<Types>(arguments)...),
evaluate(right, std::forward<Types>(arguments)...));
}
};

template<class Operation, class Left, class Right>
inline constexpr bool isExpression<BinaryOperation<Operation, Left, Right>> = true;

template<class Left, class Right>
requires (ExpressionType<Left> or ExpressionType<Right>)
constexpr auto operator*(Left&& left, Right&& right)
{
return BinaryOperation<std::multiplies<>, std::remove_cvref_t<Left>, std::remove_cvref_t<Right>>{
std::forward<Left>(left), std::forward<Right>(right)};
}

template<class Left, class Right>
requires (ExpressionType<Left> or ExpressionType<Right>)
constexpr auto operator+(Left&& left, Right&& right)
{
return BinaryOperation<std::plus<>, std::remove_cvref_t<Left>, std::remove_cvref_t<Right>>{
std::forward<Left>(left), std::forward<Right>(right)};
}

template<class Left, class Right>
requires (ExpressionType<Left> or ExpressionType<Right>)
constexpr auto operator%(Left&& left, Right&& right)
{
return BinaryOperation<std::modulus<>, std::remove_cvref_t<Left>, std::remove_cvref_t<Right>>{
std::forward<Left>(left), std::forward<Right>(right)};
}

template<class Left, class Right>
requires (ExpressionType<Left> or ExpressionType<Right>)
constexpr auto operator==(Left&& left, Right&& right)
{
return BinaryOperation<std::equal_to<>, std::remove_cvref_t<Left>, std::remove_cvref_t<Right>>{
std::forward<Left>(left), std::forward<Right>(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;
}
73 changes: 73 additions & 0 deletions tests/Functional/Placeholders.mpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
export module CppUtils.UnitTests.Functional.Placeholders;

import std;
import CppUtils;

namespace CppUtils::UnitTest::Functional::Placeholders
{
template<class Left, class Right>
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<std::vector>();

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<Args, Args>
and Addable<decltype(_0), int>
and Addable<int, decltype(_0)>
and Addable<decltype(_0), decltype(_1)>;
}>("Operators ignore unrelated types");
};
}
1 change: 1 addition & 0 deletions tests/UnitTests.mpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading