diff --git a/.gitignore b/.gitignore index 42afec2..b8b5430 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # CMake - https://github.com/github/gitignore/blob/master/CMake.gitignore +CMakeLists.txt.user CMakeCache.txt CMakeFiles CMakeScripts diff --git a/CMakeLists.txt b/CMakeLists.txt index 820a721..3ef513b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,29 @@ enable_testing() add_library(mil INTERFACE) +target_compile_options(mil + INTERFACE + + -Werror + + -Wall + -Wextra + -Wpedantic + + -Wcast-align + -Wcast-qual + -Wconversion + -Wctor-dtor-privacy + -Wenum-compare + -Wfloat-equal + -Wnon-virtual-dtor + -Wold-style-cast + -Woverloaded-virtual + -Wredundant-decls + -Wsign-conversion + -Wsign-promo +) + target_include_directories(mil INTERFACE $ diff --git a/include/chain_invoke.h b/include/chain_invoke.h index 9feded9..56fb1e0 100644 --- a/include/chain_invoke.h +++ b/include/chain_invoke.h @@ -28,6 +28,7 @@ /* STL */ #include #include +#include #include /** @@ -61,20 +62,70 @@ using namespace msl; } } + /** + * @brief TypeSelector will select what tuple of agrs will get from function + * tuple fow univsality and compatibility + * @tparam have_agrs have function args or no + * @tparam TRet return type of function + * @tparam stack_args tuple of args if function have_agrs + * @details Possible three variants which described below + // 1) if have agrs - then return type must be void and function params must + // be refs or pointers. Selected type is tuple (stack_args) + // 2) if have no agrs and return type is not ref or pointer then selected type is tuple + // 3) if have no agrs and return type is ref or pointer then selected type is tuple> + */ + template + struct TypeSelector final + { + private: + using stack_args_or_ret_value = std::conditional_t>; + inline static constexpr bool is_ref = std::is_reference_v; + inline static constexpr bool is_pointer = std::is_pointer_v; + using no_ref_but_pointer = std::tuple>>; + using ret_pointer_or_ret_value = std::conditional_t>; + + public: + inline static constexpr bool have_agrs_value = have_agrs; + inline static constexpr bool is_ref_value = is_ref; + inline static constexpr bool is_pointer_value = is_pointer; + inline static constexpr bool is_copy_value = !have_agrs_value && !is_ref_value && !is_pointer_value; + using args_pointer_value = std::conditional_t; + using type = std::conditional_t; + + TypeSelector() + { + if constexpr(have_agrs_value) + { + static_assert(std::is_same_v, "if have args then return value must be void"); + } + if constexpr(! have_agrs_value) + { + static_assert(! std::is_same_v, "if have no args then return value can't be void"); + } + } + }; + /** * @brief The invoking step object, which also holds the tuple * * @tparam Fx Type of the method */ template - struct OwningInvokingStep { - using tuple_t = typename function_info::stack_args; - using qalified_t = typename function_info::args; - using class_t = typename function_info::cl; + struct OwningInvokingStep { + using function_info_t = function_info; + using ret_t = typename function_info_t::ret; + inline static constexpr bool have_agrs = function_info_t::args_count > 0; + using type_selector_t = TypeSelector; + using tuple_t = typename type_selector_t::type; + using qalified_t = typename function_info_t::args; + using class_t = typename function_info_t::cl; - static constexpr size_t TUPLE_SIZE { std::tuple_size_v }; + // not use dicrectly but in constructor have static assert + const function_info_t function_info_; tuple_t tuple; + // only for call constructor type_selector_t and check static asserts + const type_selector_t ts; /** * @brief The streaming operator which simply deduces type of @@ -88,8 +139,34 @@ using namespace msl; */ template constexpr auto operator<<(OpFx const & aFx) && { - using invoking_t = typename function_info::cl; - return OwningInvokingStep{ aFx, std::get(tuple) }; + if constexpr (type_selector_t::have_agrs_value) + { + using invoking_t = typename function_info::cl; + return OwningInvokingStep{ aFx, std::get(tuple) }; + } + else + { + if constexpr(type_selector_t::is_copy_value) + { + static_assert(std::tuple_size_v == 1, "tuple size must be 1"); + return OwningInvokingStep{ aFx, std::get<0>(tuple) }; + } + else if constexpr(type_selector_t::is_ref_value) + { + static_assert(std::tuple_size_v == 1, "tuple size must be 1"); + return OwningInvokingStep{ aFx, std::get<0>(tuple) }; + } + else if constexpr(type_selector_t::is_pointer_value) + { + static_assert(std::tuple_size_v == 1, "tuple size must be 1"); + return OwningInvokingStep{ aFx, std::get<0>(tuple) }; + } + else + { + // type_selector_t::have_agrs_value myst be false here + static_assert(type_selector_t::have_agrs_value, "unknown compile time branch"); + } + } } /** @@ -102,7 +179,39 @@ using namespace msl; explicit constexpr OwningInvokingStep(Fx const & aFx, Obj & obj) : tuple { } { - this->invokeImpl(std::make_index_sequence{}, aFx, obj); + if constexpr (type_selector_t::have_agrs_value) + { + using compare_obj_t = std::remove_const_t>; + static_assert(std::is_same_v, compare_obj_t>, "must be one type"); + + this->invokeImpl(std::make_index_sequence{}, aFx, obj); + } + else if constexpr (type_selector_t::is_copy_value) + { + using compare_obj_t = std::remove_const_t>; + static_assert(std::is_same_v, compare_obj_t>, "must be one type"); + + this->invokeImplWithret(aFx, obj); + } + else if constexpr (type_selector_t::is_ref_value) + { + using compare_obj_t = std::remove_const_t>; + static_assert(std::is_same_v, compare_obj_t>, "must be one type"); + + this->invokeImplWithRefret(aFx, obj); + } + else if constexpr (type_selector_t::is_pointer_value) + { + using compare_obj_t = std::remove_const_t>; + static_assert(std::is_same_v, compare_obj_t>, "must be one type"); + + this->invokeImplWithPtrret(aFx, obj); + } + else + { + // type_selector_t::have_agrs_value myst be false here + static_assert(type_selector_t::have_agrs_value, "unknown compile time branch"); + } } private: /** @@ -113,8 +222,65 @@ using namespace msl; */ template constexpr void invokeImpl(std::index_sequence, Fx const & aFx, Obj & obj) { + using compare_obj_t = std::remove_const_t; + static_assert(std::is_same_v, compare_obj_t>, "must be one type"); + (obj.*aFx)(conditionalAddressOf>(std::get(tuple))...); } + template + constexpr void invokeImpl(std::index_sequence, Fx const & aFx, Obj* obj) { + using compare_obj_t = std::remove_const_t>; + static_assert(std::is_same_v, compare_obj_t>, "must be one type"); + if(obj == nullptr) { + throw std::runtime_error(std::string{"object nullptr at "} + __FUNCTION__); + } + + (obj->*aFx)(conditionalAddressOf>(std::get(tuple))...); + } + + template + constexpr void invokeImplWithret(Fx const & aFx, Obj & obj) { + using compare_obj_t = std::remove_const_t>; + static_assert(std::is_same_v, compare_obj_t>, "must be one type"); + if constexpr(std::is_pointer_v){ + if(obj == nullptr) { + throw std::runtime_error(std::string{"object nullptr at "} + __FUNCTION__); + } + } + + std::get<0>(tuple) = std::invoke(aFx, obj); + } + + template + constexpr void invokeImplWithRefret(Fx const & aFx, Obj & obj) { + using compare_obj_t = std::remove_const_t>; + static_assert(std::is_same_v, compare_obj_t>, "must be one type"); + if constexpr(std::is_pointer_v){ + if(obj == nullptr) { + throw std::runtime_error(std::string{"object nullptr at "} + __FUNCTION__); + } + } + + decltype(auto) ret_val = std::invoke(aFx, obj); + static_assert(std::is_reference_v, "ret value must be reference"); + std::get<0>(tuple) = &ret_val; + } + + template + constexpr void invokeImplWithPtrret(Fx const & aFx, Obj & obj) { + // sometimes object can be ref to pointer + using compare_obj_t = std::remove_const_t>; + static_assert(std::is_same_v, compare_obj_t>, "must be one type"); + if constexpr(std::is_pointer_v){ + if(obj == nullptr) { + throw std::runtime_error(std::string{"object nullptr at "} + __FUNCTION__); + } + } + + decltype(auto) ret_val = std::invoke(aFx, obj); + static_assert(std::is_pointer_v, "ret value must be pointer"); + std::get<0>(tuple) = ret_val; + } }; /** @@ -146,7 +312,6 @@ using namespace msl; */ template constexpr auto operator<<(OpFx const & aFx) && { - using invoking_t = typename function_info::cl; return OwningInvokingStep{ aFx, obj }; } }; diff --git a/include/function_info.h b/include/function_info.h index 424bde4..885261d 100644 --- a/include/function_info.h +++ b/include/function_info.h @@ -26,6 +26,19 @@ #include #include +// this macro will be undefined in the end this file +#ifdef MARCRO_CHECK_THAN_REF_OR_POINTER +#error "MARCRO_CHECK_THAN_REF_OR_POINTER already defined" +#endif +#define MARCRO_CHECK_THAN_REF_OR_POINTER(ClassName)\ + {\ + ClassName()\ + {\ + detail::checkThanRefOrPointer();\ + }\ + }; +// endmacro defrnition + /** * @brief msl component namespace */ @@ -46,6 +59,7 @@ namespace msl { using ret = Ret; using cl = Class; using args = std::tuple; + inline static constexpr size_t args_count = sizeof...(Args); using stack_args = std::tuple< std::remove_pointer_t< std::decay_t @@ -53,6 +67,12 @@ namespace msl { >; }; + template + void checkThanRefOrPointer() + { + static_assert(((std::is_reference_v || std::is_pointer_v) && ...), "must be pointer or reference"); + } + } /* end of namespace detal */ /** @@ -73,7 +93,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -86,7 +107,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -99,8 +121,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; - + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** * @brief The partial specialization for qualifiers @@ -112,7 +134,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -125,7 +148,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -138,7 +162,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -151,7 +176,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -164,7 +190,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -177,7 +204,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -190,7 +218,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -203,7 +232,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -216,7 +246,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -229,7 +260,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -242,7 +274,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -255,7 +288,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info\ +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -268,7 +302,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -281,7 +316,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -294,7 +330,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -307,7 +344,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -320,7 +358,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -333,7 +372,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -346,7 +386,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -359,7 +400,8 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) /** @@ -372,8 +414,14 @@ namespace msl { */ template struct function_info - : detail::method_function_info {}; + : detail::method_function_info +MARCRO_CHECK_THAN_REF_OR_POINTER(function_info) } /* end of namespace msl */ +#ifndef MARCRO_CHECK_THAN_REF_OR_POINTER +#error "have no macro MARCRO_CHECK_THAN_REF_OR_POINTER" +#endif +#undef MARCRO_CHECK_THAN_REF_OR_POINTER + #endif /* end of #ifndef INCLUDE__FUNCTION_INFO__H */ diff --git a/include/object_invoke.h b/include/object_invoke.h index ea831c3..ad55668 100644 --- a/include/object_invoke.h +++ b/include/object_invoke.h @@ -38,6 +38,10 @@ * @note MIL - Metaprogramming Invoking Library */ namespace mil { +/** + * @brief detail component namespace + */ +namespace detail { /** * @brief The delayed invoke holds information about methods chain to * be invoked. Invokes it and passes the result into the invoker @@ -77,6 +81,9 @@ namespace mil { constexpr void operator()(object_t & aObject, acceptor_t & aAcceptor) const { (*m_invokerPtr)(aObject, m_tag, aAcceptor); } + constexpr void operator()(object_t* aObject, acceptor_t & aAcceptor) const { + (*m_invokerPtr)(*aObject, m_tag, aAcceptor); + } private: /** * @brief The private invoker, performs chain invoke for the @@ -96,20 +103,14 @@ namespace mil { /** * @brief Pointer to a concrete invoker specialization */ - invoker_ptr_t m_invokerPtr; + invoker_ptr_t m_invokerPtr = nullptr; /** * @brief Associated tag */ - char const * m_tag; + char const * m_tag = nullptr; }; - - /** - * @brief detail component namespace - */ - namespace detail { - /** @{ */ /* first class meta-function */ /** @@ -161,24 +162,6 @@ namespace mil { }; } /* end of namespace detail */ - /** - * @brief Supporting struct, which holds the acceptor type - * - * @tparam TAcceptor Type of the acceptor - */ - template - struct acceptor {}; - - /** - * @brief Make the acceptor basing on the provided - * - * @tparam TAcceptor Type to use as acceptor - */ - template - constexpr inline auto useAcceptor() { - return acceptor { }; - } - /** * @brief Creates the delayed invoke (used to pass method list into the * object_invoke) @@ -210,7 +193,7 @@ namespace mil { using object_t = TObjectType; using acceptor_t = TResultAcceptor; - using delayed_invoke_t = delayed_invoke; + using delayed_invoke_t = detail::delayed_invoke; /** @@ -219,7 +202,7 @@ namespace mil { * @tparam TInvokers Types if the invokers */ template - explicit constexpr object_invoke(acceptor, TInvokers && ... aInvokers) + explicit constexpr object_invoke(acceptor_t, TInvokers && ... aInvokers) : m_delayed_invokers { aInvokers.template getDelayedInvoke()... } {} @@ -238,7 +221,7 @@ namespace mil { /* class deduction guides */ template - explicit object_invoke(acceptor, T ...) -> object_invoke::cl, sizeof...(T), TResultAcceptor>; + explicit object_invoke(TResultAcceptor, T ...) -> object_invoke::cl, sizeof...(T), TResultAcceptor>; } /* end of namespace mil */ diff --git a/test/testMain.cpp b/test/testMain.cpp index 5925cdb..4841845 100644 --- a/test/testMain.cpp +++ b/test/testMain.cpp @@ -25,6 +25,11 @@ #include +#ifdef PRINT_DEBUG_INFO +#error "PRINT_DEBUG_INFO already used" +#endif +//#define PRINT_DEBUG_INFO + template struct InstanceCounter { static size_t instances; @@ -39,16 +44,20 @@ struct InstanceCounter { void incAndLog() const { ++instances; +#ifdef PRINT_DEBUG_INFO std::cout << " +++New object \'" << typeid(T).name() << "\', addr: \'" << static_cast(this) << "\' created, total: \'" << instances << "\'" << std::endl; +#endif } void decAndLog() const { --instances; +#ifdef PRINT_DEBUG_INFO std::cout << " ---Object \'" << typeid(T).name() << "\', addr: \'" << static_cast(this) << "\' destroyed, total: \'" << instances << "\'" << std::endl; +#endif } }; @@ -57,27 +66,114 @@ size_t InstanceCounter::instances { 0ull }; struct Object1 : public InstanceCounter { - void getValue(int & i) { - // std::cout << " getValue invoked!" << std::endl; - static int ii { 11111 }; - ii += 11111; + void CStyleGetValue(int & i) const { +#ifdef PRINT_DEBUG_INFO + std::cout << __FUNCTION__ << " invoked!" << std::endl; +#endif + ii += 1; i = ii; } + + int getValue() const + { +#ifdef PRINT_DEBUG_INFO + std::cout << __FUNCTION__ << std::endl; +#endif + ii += 1; + return ii; + } + + const int& refValue() const + { +#ifdef PRINT_DEBUG_INFO + std::cout << __FUNCTION__ << std::endl; +#endif + ii += 1; + return ii; + } + + const int* ptrValue() const + { +#ifdef PRINT_DEBUG_INFO + std::cout << __FUNCTION__ << std::endl; +#endif + ii += 1; + return ⅈ + } + + inline static int ii { 0 }; }; struct Object2 : public InstanceCounter { - void getObject1(Object1 & obj) { - // std::cout << " getObject1 invoked!" << std::endl; - (void)obj; + void CStyleGetObject1(Object1 & obj) const { +#ifdef PRINT_DEBUG_INFO + std::cout << __FUNCTION__ << " invoked!" << std::endl; +#endif + obj = obj_; + } + + Object1 getObject1() const { +#ifdef PRINT_DEBUG_INFO + std::cout << __FUNCTION__ << " invoked" << std::endl; +#endif + return obj_; + } + + const Object1& refObject1() const { +#ifdef PRINT_DEBUG_INFO + std::cout << __FUNCTION__ << " invoked" << std::endl; +#endif + return obj_; } + + const Object1& ptrObject1() const { +#ifdef PRINT_DEBUG_INFO + std::cout << __FUNCTION__ << " invoked" << std::endl; +#endif + return obj_; + } + + Object1 obj_; }; struct Object3 : public InstanceCounter { - void getObject2(Object2 * obj) { - //std::cout << " getObject2 invoked!" << std::endl; + void CStyleGetObject2(Object2 * obj) const { +#ifdef PRINT_DEBUG_INFO + std::cout << __FUNCTION__ << " invoked!" << std::endl; +#endif + *obj = obj_; + } + + Object2 getObject2() const { +#ifdef PRINT_DEBUG_INFO + std::cout << __FUNCTION__ << " invoked!" << std::endl; +#endif + return obj_; + } + + const Object2& refObject2() { +#ifdef PRINT_DEBUG_INFO + std::cout << __FUNCTION__ << " invoked!" << std::endl; +#endif + return obj_; + } + + const Object2* ptrObject2() { +#ifdef PRINT_DEBUG_INFO + std::cout << __FUNCTION__ << " invoked!" << std::endl; +#endif + return &obj_; + } + + void testFail(int c) + { + c = 15; + std::cout << c << std::endl; } + + Object2 obj_; }; struct Serializer { @@ -88,28 +184,87 @@ struct Serializer { std::cout << "\'\n"; } + template + static auto& get_wrapper(const T& val) + { + constexpr bool is_ponter = std::is_pointer_v>; + if constexpr(is_ponter) { + return *val; + } else { + return val; + } + } + template static std::ostream & putStream(std::ostream & aOs, Tuple const & t, std::index_sequence) { - return (aOs << ... << std::get(t)); + (aOs << ... << get_wrapper(std::get(t))); + + return aOs; } }; +template +struct value { + constexpr inline static auto F = val; +}; +template +using values_tuple = std::tuple...>; -constexpr mil::object_invoke invoke { - mil::useAcceptor(), - mil::delayedInvoke<&Object3::getObject2, &Object2::getObject1, &Object1::getValue>("call1"), - mil::delayedInvoke<&Object3::getObject2, &Object2::getObject1, &Object1::getValue>("call2"), - mil::delayedInvoke<&Object3::getObject2, &Object2::getObject1, &Object1::getValue>("call3"), - mil::delayedInvoke<&Object3::getObject2, &Object2::getObject1, &Object1::getValue>("call4") -}; +using obj3_methods_t = values_tuple<&Object3::CStyleGetObject2, &Object3::getObject2, &Object3::refObject2, &Object3::ptrObject2>; +using obj2_methods_t = values_tuple<&Object2::CStyleGetObject1, &Object2::getObject1, &Object2::refObject1, &Object2::ptrObject1>; +using obj1_methods_t = values_tuple<&Object1::CStyleGetValue, &Object1::getValue, &Object1::refValue, &Object1::ptrValue>; +template +void constexpr one_test(Object3& obj, Serializer& si) +{ + constexpr mil::object_invoke invoke { + Serializer{}, + mil::delayedInvoke("tag1") + }; -int main() { - Object3 obj {}; + invoke(obj, si); + + { + const auto invoke_forwarder = mil::delayedInvoke("tag2"); + const auto delayed_invoker = invoke_forwarder.template getDelayedInvoke(); + + delayed_invoker(obj, si); + } +} + +template +void constexpr cross_test(Object3& obj, Serializer& si, + const obj3_methods_t& obj3_methods, const obj2_methods_t& obj2_methods, const obj1_methods_t& obj1_methods) +{ + one_test(obj3_methods).F, std::get(obj2_methods).F, std::get(obj1_methods).F>(obj, si); + if constexpr(NObj2 != 0) { + cross_test(obj, si, obj3_methods, obj2_methods, obj1_methods); + + if constexpr(NObj1 != 0) { + cross_test(obj, si, obj3_methods, obj2_methods, obj1_methods); + } + } +} + +template +void constexpr block_test() +{ + Object3 obj; Serializer si; - invoke(obj, si); + const obj3_methods_t obj3_methods{}; + const obj2_methods_t obj2_methods{}; + const obj1_methods_t obj1_methods{}; + + cross_test(obj, si, obj3_methods, obj2_methods, obj1_methods); + if constexpr (N != 0) { + cross_test(obj, si, obj3_methods, obj2_methods, obj1_methods); + } +} + +int main() { + block_test<3>(); return 0; }