Skip to content

Commit

Permalink
Small experimental matcher for variants
Browse files Browse the repository at this point in the history
  • Loading branch information
vt4a2h committed Jan 10, 2025
1 parent 111d1ad commit 13c9164
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/include/fl/expected/expected.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
//
// MIT License
//
// Copyright (c) 2024-present Vitaly Fanaskov
//
// fl -- Functional tools for C++
// Project home: https://github.com/vt4a2h/fl
//
// See LICENSE file for the further details.
//
#include <variant>

namespace fl {
Expand Down
44 changes: 44 additions & 0 deletions src/include/fl/utils/callable_traits.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// MIT License
//
// Copyright (c) 2025-present Vitaly Fanaskov
//
// fl -- Functional tools for C++
// Project home: https://github.com/vt4a2h/fl
//
// See LICENSE file for the further details.
//
#pragma once

#include <tuple>

namespace fl {

template<class T>
struct callable_traits;

template<class T>
struct callable_traits : public callable_traits<decltype(&T::operator())> {};

template<class R, class ...Args>
struct callable_traits<R (*)(Args...)>
{
static constexpr std::size_t args_count = sizeof...(Args);

using result_type = R;

template <std::size_t index>
requires (index < args_count)
struct arg
{
using type = typename std::tuple_element<index, std::tuple<Args...>>::type;
};
};

template<class R, class C, class ...Args>
struct callable_traits<R (C::*)(Args...) const> : public callable_traits<R (*)(Args...)>
{
using class_type = C;
};

} // namespace fl
71 changes: 71 additions & 0 deletions src/include/fl/utils/match.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// MIT License
//
// Copyright (c) 2025-present Vitaly Fanaskov
//
// fl -- Functional tools for C++
// Project home: https://github.com/vt4a2h/fl
//
// See LICENSE file for the further details.
//
#pragma once

#include <variant>
#include <concepts>
#include <type_traits>

namespace fl {

namespace details {

template <class>
constexpr bool is_variant = false;

template <class ...Args>
constexpr bool is_variant<std::variant<Args...>> = true;

template <class Variant>
concept IsVariant = is_variant<std::remove_cvref_t<Variant>>;

template <class F, IsVariant Variant>
constexpr bool invocable_with()
{
return []<std::size_t... I>(std::index_sequence<I...>){
return (... || std::is_invocable_v<F, std::variant_alternative_t<I, Variant>>);
}(std::make_index_sequence<std::variant_size_v<Variant>>{});
}

template <class F, class Variant>
concept InvocableWithAnyOf = invocable_with<std::remove_cvref_t<F>, std::remove_cvref_t<Variant>>();

template <std::size_t Index, class Variant, class Value, class Callable>
bool invokeIfCan(Value &valuePtr, Callable f)
{
if constexpr (std::is_invocable_v<Callable, std::variant_alternative_t<Index, Variant>>) {
std::invoke(f, valuePtr);
return true;
} else {
return false;
}
}

template <std::size_t Index, class Variant_t, details::IsVariant Variant, details::InvocableWithAnyOf<Variant> ...Callable>
bool invokeIfHoldsCorrectAlternative(const Variant &variant, const Callable &...callable)
{
auto *v = std::get_if<std::variant_alternative_t<Index, Variant_t>>(&variant);
return v && ((... || invokeIfCan<Index, Variant_t>(*v, callable)));
}

} // namespace details

template <details::IsVariant Variant, details::InvocableWithAnyOf<Variant> ...Callable>
void match(const Variant &variant, Callable ...callable)
{
using Variant_t = std::remove_cvref_t<Variant>;

[&variant, &callable...]<std::size_t... I>(std::index_sequence<I...>){
(... || details::invokeIfHoldsCorrectAlternative<I, Variant_t>(variant, callable...));
}(std::make_index_sequence<std::variant_size_v<Variant_t>>{});
}

} // namespace fl
2 changes: 2 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ target_sources(tests PRIVATE
test_writer_move_copy.cpp
# test_utils_ap_optional.cpp
# test_applicative_optional.cpp
test_match.cpp
expected/test_expected_create.cpp
expected/test_expected_unexpected_create.cpp
expected/test_expected_unexpected_access_error.cpp
Expand Down Expand Up @@ -57,6 +58,7 @@ if(MSVC)
target_compile_options(tests
PRIVATE
"/wd4996;" # external warning in fmt
"/wd2220;" # convert int/double in tests
)
endif()

Expand Down
29 changes: 29 additions & 0 deletions test/test_match.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// MIT License
//
// Copyright (c) 2025-present Vitaly Fanaskov
//
// fl -- Functional tools for C++
// Project home: https://github.com/vt4a2h/fl
//
// See LICENSE file for the further details.
//
#include "catch.hpp"

#include <fl/utils/match.hpp>

#include <fmt/format.h>

template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };

TEST_CASE("Invoked for all alternatives")
{
auto intHandler = [](int v){ fmt::println("int: {}", v); };
auto stringHandler = [](const std::string &v){ fmt::println("string: {}", v); };
auto doubleHandler = [](double v){ fmt::println("double: {}", v); };

std::variant<int, std::string, double> v = 42;

fl::match(v, stringHandler, intHandler, doubleHandler);
}

0 comments on commit 13c9164

Please sign in to comment.