Using reflection for getting the typelist of a class constructor

251 Views Asked by At

I need a template that receives a class as a type, and returns a list of types from its constructor. I tried to use the first answer of this SO question, but it only works for some classes. This header contains templates for getting the typelist of a class constructor in a tuple form.

In my use case, I end up getting constexpr expansion limit (512 is the default) and template instantiation depth limit (900 is the default) errors when I try to use the fields_number_ctor template. Here is the full header implementation:

#include <tuple>
#include <utility>

// Based on
// * http://alexpolt.github.io/type-loophole.html
//   https://github.com/alexpolt/luple/blob/master/type-loophole.h
//   by Alexandr Poltavsky, http://alexpolt.github.io
// * https://www.youtube.com/watch?v=UlNUNxLtBI0
//   Better C++14 reflections - Antony Polukhin - Meeting C++ 2018

namespace refl {

// tag<T, N> generates friend declarations and helps with overload resolution.
// There are two types: one with the auto return type, which is the way we read types later.
// The second one is used in the detection of instantiations without which we'd get multiple
// definitions.
template <typename T, int N>
struct tag {
    friend auto loophole(tag<T, N>);
    constexpr friend int cloophole(tag<T, N>);
};

// The definitions of friend functions.
template <typename T, typename U, int N, bool B,
          typename = typename std::enable_if_t<
            !std::is_same_v<
              std::remove_cv_t<std::remove_reference_t<T>>,
              std::remove_cv_t<std::remove_reference_t<U>>>>>
struct fn_def {
    friend auto loophole(tag<T, N>) { return U{}; }
    constexpr friend int cloophole(tag<T, N>) { return 0; }
};

// This specialization is to avoid multiple definition errors.
template <typename T, typename U, int N> struct fn_def<T, U, N, true> {};

// This has a templated conversion operator which in turn triggers instantiations.
// Important point, using sizeof seems to be more reliable. Also default template
// arguments are "cached" (I think). To fix that I provide a U template parameter to
// the ins functions which do the detection using constexpr friend functions and SFINAE.
template <typename T, int N>
struct c_op {
    template <typename U, int M>
    static auto ins(...) -> int;
    template <typename U, int M, int = cloophole(tag<T, M>{})>
    static auto ins(int) -> char;

    template <typename U, int = sizeof(fn_def<T, U, N, sizeof(ins<U, N>(0)) == sizeof(char)>)>
    operator U();
};

// Here we detect the data type field number. The byproduct is instantiations.
// Uses list initialization. Won't work for types with user-provided constructors.
// Since C++17 there is std::is_aggregate which can be added later.
template <typename T, int... Ns>
constexpr int fields_number(...) { return sizeof...(Ns) - 1; }

template <typename T, int... Ns>
constexpr auto fields_number(int) -> decltype(T{c_op<T, Ns>{}...}, 0) {
    return fields_number<T, Ns..., sizeof...(Ns)>(0);
}

// Here is a version of fields_number to handle user-provided ctor.
// NOTE: It finds the first ctor having the shortest unambigious set
//       of parameters.
template <typename T, int... Ns>
constexpr auto fields_number_ctor(int) -> decltype(T(c_op<T, Ns>{}...), 0) {
    return sizeof...(Ns);
}

template <typename T, int... Ns>
constexpr int fields_number_ctor(...) {
    return fields_number_ctor<T, Ns..., sizeof...(Ns)>(0);
}

// This is a helper to turn a ctor into a tuple type.
// Usage is: refl::as_tuple<data_t>
template <typename T, typename U> struct loophole_tuple;

template <typename T, int... Ns>
struct loophole_tuple<T, std::integer_sequence<int, Ns...>> {
    using type = std::tuple<decltype(loophole(tag<T, Ns>{}))...>;
};

template <typename T>
using as_tuple =
    typename loophole_tuple<T, std::make_integer_sequence<int, fields_number_ctor<T>(0)>>::type;

}  // namespace refl

How i can get rid of the expansions errors? I also accept recomendations/examples of how can i do the same using another open source reflection C++ library.

EDIT:

I'm calling the template this way:

#include <string>
#include <vector>
#include <iosfwd>
#include <iostream>
#include "utils/reflection.hpp"
#include "src/contract/simplecontract.h"



int main() {
    constexpr static auto data_ctor_nparams = refl::fields_number_ctor<SimpleContract>();
    std::cout << data_ctor_nparams << std::endl;
    using data_ctor_type = refl::as_tuple<MyClass>;

    std::cout << std::is_same_v<std::tuple_element_t<0, data_ctor_type>, int> << std::endl;
    std::cout << std::is_same_v<std::tuple_element_t<1, data_ctor_type>, float> << std::endl;
    std::cout << std::is_same_v<std::tuple_element_t<2, data_ctor_type>, std::string> << std::endl;
    
    
}


And this is the relevant part of the SimpleContract class:

class SimpleContract : public DynamicContract {
  private:
    // string name
    SafeString name;
    // uint256 value
    SafeUint256_t value;

    void registerContractFunctions() override;
  public:
    /// Create new contract with given name and value.
    SimpleContract(const std::string& name,
                   uint256_t value,
                   ContractManager::ContractManagerInterface &interface,
                   const Address& address,
                   const Address& creator,
                   const uint64_t& chainId,
                   const std::unique_ptr<DB> &db);

    /// Load contract from database.
    SimpleContract(ContractManager::ContractManagerInterface &interface,
                   const Address& address,
                   const std::unique_ptr<DB> &db);

...

0

There are 0 best solutions below