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);
...