From a9a3e1bf4369410c446fc5514e6cc065e7c9e16d Mon Sep 17 00:00:00 2001
From: Brett Laptop <tri11paragon@tpgc.me>
Date: Tue, 22 Apr 2025 21:05:44 -0400
Subject: [PATCH] variants closer to working

---
 CMakeLists.txt                |   2 +-
 include/blt/logging/logging.h |   2 +-
 include/blt/std/variant.h     | 148 ++++++++--------------------------
 libraries/parallel-hashmap    |   2 +-
 src/blt/parse/obj_loader.cpp  |   2 +-
 tests/variant_tests.cpp       |  26 +++++-
 6 files changed, 61 insertions(+), 121 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9ac8043..a165a31 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
 cmake_minimum_required(VERSION 3.20)
 include(cmake/color.cmake)
-set(BLT_VERSION 5.4.6)
+set(BLT_VERSION 5.4.7)
 
 set(BLT_TARGET BLT)
 
diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h
index bc2e7a4..e9c56f8 100644
--- a/include/blt/logging/logging.h
+++ b/include/blt/logging/logging.h
@@ -62,7 +62,7 @@ namespace blt::logging
 		template <size_t index, typename T>
 		void handle_func(const T& t)
 		{
-			m_arg_print_funcs[index] = [&t, this](std::ostream& stream, const fmt_spec_t& type) {
+			m_arg_print_funcs[index] = [&t](std::ostream& stream, const fmt_spec_t& type) {
 				switch (type.sign)
 				{
 					case fmt_sign_t::SPACE:
diff --git a/include/blt/std/variant.h b/include/blt/std/variant.h
index aa56eae..c20eb3e 100644
--- a/include/blt/std/variant.h
+++ b/include/blt/std/variant.h
@@ -45,128 +45,50 @@ namespace blt
 		template <typename T, typename... Ts>
 		struct filter_void<T, Ts...>
 		{
-			using type = std::conditional_t<std::is_same_v<T, void>, typename filter_void<Ts...>::type, decltype(std::tuple_cat(
+			using type = std::conditional_t<std::is_void_v<T>, typename filter_void<Ts...>::type, decltype(std::tuple_cat(
 												std::declval<std::tuple<T>>(), std::declval<typename filter_void<Ts...>::type>()))>;
 		};
 
 		template <typename... Ts>
 		using filter_void_t = typename filter_void<Ts...>::type;
 
-		template <typename Type, typename... Ts>
-		struct filter_invoke;
-
-		template <typename Type>
-		struct filter_invoke<Type>
-		{
-			using type = std::tuple<>;
-		};
-
-		template <typename Type, typename T, typename... Ts>
-		struct filter_invoke<Type, T, Ts...>
-		{
-			using type = std::conditional_t<std::is_invocable_v<T, Type>, decltype(std::tuple_cat(
-												std::declval<std::tuple<T>>(), std::declval<typename filter_invoke<Ts...>::type>())),
-											typename filter_invoke<Ts...>::type>;
-		};
-
-		template <typename... Ts>
-		using filter_invoke_t = typename filter_invoke<Ts...>::type;
-
-		template <typename Type, typename Func>
-		struct member_func_meta
-		{
-			using can_invoke = std::is_invocable<Func, Type>;
-
-			using return_type = std::conditional_t<can_invoke::value, std::invoke_result_t<Func, Type>, void>;
-		};
-
-		template <typename T, typename Func, size_t Index>
-		struct passthrough_value
+		template<typename T>
+		struct passthrough
 		{
 			using type = T;
-			using func = Func;
-			constexpr static size_t index = Index;
-
-			bool has_value;
-
-			explicit passthrough_value(const bool has_value): has_value(has_value)
-			{}
-
-			explicit operator bool() const
-			{
-				return has_value;
-			}
 		};
 
-		template <typename Type, typename... Funcs>
-		struct first_invoke_member_func
+		template <typename Type, typename Func, typename... Args>
+		struct member_func_meta
 		{
-			template <size_t... Indexes>
-			constexpr static auto find_func(std::index_sequence<Indexes...>)
-			{
-				return (... || []() {
-					using Meta = member_func_meta<Type, Funcs>;
-					if constexpr (Meta::can_invoke::value)
-					{
-						return passthrough_value<typename Meta::return_type, Funcs, Indexes>{true};
-					}
-					return passthrough_value<typename Meta::return_type, Funcs, Indexes>{false};
-				}());
-			}
+			using can_invoke = std::is_invocable<Func, Type, Args...>;
 
-			using result = decltype(find_func(std::index_sequence_for<Funcs...>()));
-			using return_type = typename result::type;
-			using func_type = typename result::func;
-			constexpr static size_t function_index = result::index;
+			using return_type = typename std::conditional_t<can_invoke::value, std::invoke_result<Func, Type, Args...>, passthrough<void>>::type;
 		};
 
-		template <typename FuncTuple, typename ArgTuple>
-		struct member_func_detail;
+		template <typename Func, typename ArgsTuple, typename... Types>
+		struct member_call_return_type;
 
-		template <typename... Funcs, typename... Args>
-		struct member_func_detail<std::tuple<Funcs...>, std::tuple<Args...>>
+		template <typename Func, typename... Args, typename... Types>
+		struct member_call_return_type<Func, std::tuple<Args...>, Types...>
 		{
-			using result_types = std::tuple<first_invoke_member_func<Args, Funcs...>...>;
-			using base_type = typename std::tuple_element_t<0, result_types>::return_type;
+			using result_types = std::tuple<member_func_meta<Types, Func, Args...>...>;
+			using non_void_result_types = typename filter_void<typename member_func_meta<Types, Func, Args...>::return_type...>::type;
 
-			template <typename T>
-			using get_type = typename T::return_type;
+			static constexpr bool all_void = std::tuple_size_v<non_void_result_types> == 0;
+			static constexpr bool some_void = std::tuple_size_v<non_void_result_types> != sizeof...(Types);
 
-			template <typename T>
-			using is_base = std::is_same<T, base_type>;
+			using first_return = std::conditional_t<all_void, void, std::tuple_element_t<0, non_void_result_types>>;
 
-			template <typename T>
-			using is_base_or_void = std::disjunction<std::is_void<typename T::return_type>, is_base<typename T::return_type>>;
-
-			template <template<typename...> typename Functor, template<typename> typename PerType, size_t... Indexes>
-			constexpr static auto for_each_type(std::index_sequence<Indexes...>)
-			{
-				return std::declval<Functor<PerType<std::tuple_element_t<Indexes, result_types>>...>>;
-			}
-
-			constexpr static bool all_has_void = std::decay_t<std::invoke_result_t<decltype(for_each_type<std::conjunction, std::is_void>(
-				std::index_sequence_for<Args...>()))>>::value;
-			constexpr static bool all_has_ret = std::decay_t<std::invoke_result_t<decltype(for_each_type<std::conjunction, is_base>(
-				std::index_sequence_for<Args...>()))>>::value;
-			constexpr static bool all_has_ret_or_void = std::decay_t<std::invoke_result_t<decltype(for_each_type<std::conjunction, is_base_or_void>(
-				std::index_sequence_for<Args...>()))>>::value;
-
-			using non_void_types = typename std::decay_t<std::invoke_result_t<decltype(for_each_type<filter_void, get_type>(
-				std::index_sequence_for<Args...>()))>>::type;
-
-			template <size_t... Indexes>
-			static constexpr auto make_variant(std::index_sequence<Indexes...>)
-			{
-				using variant = variant_t<std::decay_t<std::tuple_element_t<Indexes, non_void_types>>...>;
-				return std::declval<variant>();
-			}
-
-			using make_return_type = std::conditional_t<all_has_void, void, std::conditional_t<
-															all_has_ret, base_type, std::conditional_t<
-																all_has_ret_or_void, std::optional<base_type>, std::conditional_t<
-																	std::tuple_size_v<non_void_types> == 0, void, decltype(make_variant(
-																		std::make_index_sequence<std::tuple_size_v<non_void_types>>{}))>>>>;
+			using return_type = std::conditional_t<all_void, void, std::conditional_t<some_void, std::optional<first_return>, first_return>>;
 		};
+
+		template <typename FuncTuple, typename TypesTuple>
+		struct visit_return_type;
+
+		template <typename... Funcs, typename... Types>
+		struct visit_return_type<std::tuple<Funcs...>, std::tuple<Types...>>
+		{};
 	}
 
 	/*
@@ -278,8 +200,7 @@ namespace blt
 		* @param visitees user lambdas
 		*/
 		template <typename... Visitee>
-		constexpr auto visit(
-			Visitee&&... visitees) -> typename detail::member_func_detail<std::tuple<Visitee...>, std::tuple<Types...>>::make_return_type
+		constexpr auto visit(Visitee&&... visitees) -> decltype(auto)
 		{
 			return std::visit(lambda_visitor{std::forward<Visitee>(visitees)...}, m_variant);
 		}
@@ -287,21 +208,20 @@ namespace blt
 		template <typename Default, typename... Visitee>
 		constexpr auto visit_value(Default&& default_value, Visitee&&... visitees) -> decltype(auto)
 		{
-			return std::visit(lambda_visitor{
-				std::forward<Visitee>(visitees)...,
-				[default_value=std::forward<Default>(default_value)](auto&& value) {
-					return std::forward<decltype(value)>(value);
-				}
+			return visit(std::forward<Visitee>(visitees)..., [default_value=std::forward<Default>(default_value)](auto&&) {
+				return std::forward<Default>(default_value);
 			});
 		}
 
 		template <typename MemberFunc, typename... Args>
-		constexpr auto call_member(const MemberFunc func,
-									Args&&... args) -> typename detail::member_func_detail<
-			std::tuple<MemberFunc>, std::tuple<Types...>>::make_return_type
+		constexpr auto call_member(const MemberFunc func, Args&&... args)
 		{
-			return std::visit([func,...args=std::forward<Args>(args)](auto&& value) {
-				return ((value).*(func))(std::forward<Args>(args)...);
+			using meta = detail::member_call_return_type<MemberFunc, std::tuple<Args...>, Types...>;
+			return std::visit([&](auto&& value) -> typename meta::return_type {
+				if constexpr (std::is_invocable_v<MemberFunc, decltype(value), Args...>)
+					return ((value).*(func))(std::forward<Args>(args)...);
+				else
+					return {};
 			}, m_variant);
 		}
 
diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap
index 93201da..7ef2e73 160000
--- a/libraries/parallel-hashmap
+++ b/libraries/parallel-hashmap
@@ -1 +1 @@
-Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3
+Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5
diff --git a/src/blt/parse/obj_loader.cpp b/src/blt/parse/obj_loader.cpp
index 1449d60..5f0a2bc 100644
--- a/src/blt/parse/obj_loader.cpp
+++ b/src/blt/parse/obj_loader.cpp
@@ -236,7 +236,7 @@ namespace blt::parse
 			auto loc = vertex_map.find(face);
 			if (loc == vertex_map.end())
 			{
-				BLT_DEBUG("DID NOT FIND FACE!");
+				BLT_DEBUG("{}", "DID NOT FIND FACE!");
 				auto index = static_cast<std::int32_t>(vertex_data.size());
 				vertex_data.push_back({vertices[vi], uvs[ui], normals[ni]});
 				BLT_DEBUG("Vertex: ({.4f}, {.4f}, {.4f}), UV: ({.4f}, {.4f}), Normal: ({.4f}, {.4f}, {:.4f})", vertices[vi].x(), vertices[vi].y(),
diff --git a/tests/variant_tests.cpp b/tests/variant_tests.cpp
index 9adecae..b0c2e81 100644
--- a/tests/variant_tests.cpp
+++ b/tests/variant_tests.cpp
@@ -121,15 +121,35 @@ struct storing_type2 final : mutate_type
 	float internal;
 };
 
+struct no_members
+{
+	int hello;
+};
+
 int main()
 {
-
 	blt::variant_t<type1, type2, type3> v1{type1{}};
 	blt::variant_t<type1, type2, type3> v2{type2{}};
 	blt::variant_t<type1, type2, type3> v3{type3{}};
 
 	BLT_TRACE("Variants to_string():");
-	BLT_TRACE("V1: {}", v1.call_member(&base_type::to_string));
-	BLT_TRACE("V2: {}", v2.call_member(&base_type::to_string));
+
+	auto v1_result = v1.call_member(&base_type::to_string);
+	BLT_ASSERT_MSG(v1_result == type1{}.to_string(), ("Expected result to be " + type1{}.to_string() + " but found " + v1_result).c_str());
+	BLT_ASSERT_MSG(typeid(v1_result) == typeid(std::string), "Result type expected to be string!");
+	BLT_TRACE("V1: {}", v1_result);
+
+	auto v2_result = v2.call_member(&base_type::to_string);
+
+	BLT_TRACE("V2: {}", v2_result);
 	BLT_TRACE("V3: {}", v3.call_member(&base_type::to_string));
+
+	blt::variant_t<type1, type2, no_members> member_missing_stored_member{type1{}};
+	blt::variant_t<type1, type2, no_members> member_missing_stored_no_member{no_members{50}};
+
+	auto stored_member_result = member_missing_stored_member.call_member(&base_type::to_string);
+	auto no_member_result = member_missing_stored_no_member.call_member(&base_type::to_string);
+
+	BLT_TRACE("Stored: has value? '{}' value: '{}'", stored_member_result.has_value(), *stored_member_result);
+	BLT_TRACE("No Member: {}", no_member_result.has_value());
 }
\ No newline at end of file