c++dependency-injectionconstructormetaprogrammingsfinae

How to get constructor parameter types as a tuple in C++?


Unable to retrieve constructor arguments in a tuple.

I am trying to extract the constructor parameter types of a given class as a std::tuple. For example, if I have a class like this:

struct MyClass {
    MyClass(int, double, std::string) {}
};

I want to obtain a type like std::tuple<int, double, std::string> corresponding to the constructor parameters.

I am aware that in Boost.DI, this seems to be achievable since they manage to resolve constructor dependencies automatically. However, I couldn’t find a clear explanation of how they extract the constructor’s parameter types.

For regular functions, I know how to use std::tuple with templates, as shown below:

template <typename Func>
struct function_traits;

// Specialization for functions
template <typename Ret, typename... Args>
struct function_traits<Ret(Args...)> {
    using args_tuple = std::tuple<Args...>;
};

However, I am unsure how to adapt this approach for constructors.

Constraints:

Could someone guide me on how to achieve this? Perhaps a minimal example or an explanation of how Boost.DI does it?

In case there are multiple constructors, boost di says it chooses the longest constructor. how does it do that too?

Example of how boost di works (from the official documentation). ref : boost di exemple

// $CXX -std=c++14 basic_create_objects_tree.cpp
#include <boost/di.hpp>

namespace di = boost::di;

struct renderer {
  int device;
};

class view {
 public:
  view(std::string /*title*/, const renderer&) {}
};

class model {};

class controller {
 public:
  controller(model&, view&) {}
};

class user {};

class app {
 public:
  app(controller&, user&) {}
};

int main() {
  /**
   * renderer renderer_;
   * view view_{"", renderer_};
   * model model_;
   * controller controller_{model_, view_};
   * user user_;
   * app app_{controller_, user_};
   */

  auto injector = di::make_injector();
  injector.create<app>();
}

I think I have a clue using this file but I still can't fully understand the system and how I could implement it. boost/di/type_traits/ctor_traits.hpp


Solution

  • First, count how many arguments they are (using Ted Lyngmo's code) (with hardcoded limit):

    template<class T, std::size_t I>
    struct Any {
        template<class U>
        operator U() const;
    };
    
    template <class T, size_t N>
    struct is_valid_amount {
        static std::false_type test(...);
    
        template <std::size_t... Is>
        static auto test(std::index_sequence<Is...>)
            -> decltype(T(Any<T, Is>{}...), std::true_type{});
    
        static constexpr bool value =
            decltype(test(std::make_index_sequence<N>{}))::value;
    };
    template <class T, size_t N>
    inline constexpr bool is_valid_amount_v = is_valid_amount<T, N>::value;
    
    template<class T, std::size_t N>
    struct ctor_def : std::integral_constant<std::size_t, N> {
    };
    
    template <class T, std::size_t N = 16> // increase max amount of args if you wish
    struct ctor_arg_count
        : std::conditional_t<is_valid_amount_v<T, N>,
                            ctor_def<T, N>,
                            ctor_arg_count<T, N - 1>> {};
    template<class T>
    inline constexpr std::size_t ctor_arg_count_v = ctor_arg_count<T>::value;
    

    Then, with stateful metaprogramming, you might do:

    namespace details
    {
       template<std::size_t N> struct tag{};
    
        template<typename T, std::size_t N>
        struct loophole_t {
            friend auto loophole(details::tag<N>) { return std::type_identity<T>{}; };
        };
    
        auto loophole(tag<0>);
        auto loophole(tag<1>);
        auto loophole(tag<2>);
        auto loophole(tag<3>);
        auto loophole(tag<4>);
        auto loophole(tag<5>);
        auto loophole(tag<6>);
        auto loophole(tag<7>);
        auto loophole(tag<8>); // Add extra
    
        template <std::size_t N>
        struct detector {
            template <class T, std::size_t = sizeof(details::loophole_t<T, N>)>
            operator T();
        };
    
        template<typename T, typename Seq> struct ins;
    
        template<typename T, std::size_t... Is>
        struct ins<T, std::index_sequence<Is...>>
        {
            template <std::size_t = sizeof(T{detector<Is>{}...})>
            constexpr int operator()() const { return 0; }
        };
    
        template <std::size_t, std::size_t... Is, typename T = std::tuple<typename decltype(loophole(details::tag<Is>{}))::type...>>
        T get_type_impl(std::index_sequence<Is...>);
    
        template <typename T, std::size_t N = details::ctor_arg_count_v<T>>
        auto get_type()
        {
            return details::get_type_impl<details::ins<T, std::make_index_sequence<N>>{}()>(std::make_index_sequence<N>());
        }
    
    }
    
    template <typename T>
    using constructor_type_as_tuple_t = decltype(details::get_type<T>());
    

    Demo