Search code examples
c++templatesconstructorpreprocessorauto-generate

Determine the number and type of the class constructor's parameters in C++?


How could one determine the number and type of the class constructor's parameters? To do that for a member function is just a piece of cake:

template <class T, typename P0, typename P1, typename P2, typename P3>
void BindNativeMethod( void (T::*MethodPtr)(P0, P1, P2, P3) )
{
   // we've got 4 params
   // use them this way:
   std::vector<int> Params;
   Params.push_back( TypeToInt<P0>() );
   Params.push_back( TypeToInt<P1>() );
   Params.push_back( TypeToInt<P2>() );
   Params.push_back( TypeToInt<P3>() );
}

template <class T, typename P0, typename P1, typename P2, typename P3, typename P4>
void BindNativeMethod( void (T::*MethodPtr)(P0, P1, P2, P3, P4) )
{
   // we've got 5 params
   // use them this way:
   std::vector<int> Params;
   Params.push_back( TypeToInt<P0>() );
   Params.push_back( TypeToInt<P1>() );
   Params.push_back( TypeToInt<P2>() );
   Params.push_back( TypeToInt<P3>() );
   Params.push_back( TypeToInt<P4>() );
}

and so on for other members.

But what to do with the class constructors? Is there any way to find out the type of their arguments? Maybe there's a fundamentally different approach to solve this because it's even impossible to take the address of the constructor?

Edit: I have a C++ preprocessor that scans all source files and has the database of all classes, methods, ctors and their exact prototypes. I need to generate some stubs based on this.


Solution

  • If I understand your requirement correctly, you want a function that you can take the address of that tells you the types of the parameters to the constructor or constructors.

    #include <string>
    #include <new>
    
    template <typename T, typename P0, typename P1>
    T * fake_ctor (T *mem, P0 p0, P1 p1) {
        return mem ? new (mem) T(p0, p1) : 0;
    }
    
    struct Foo {
        Foo (int, int) {}
        Foo (int, std::string) {}
    };
    
    template <class T, typename P0, typename P1>
    void BindClassCtor(T *(*FakeCtor)(T *, P0, P1)) {
        std::vector<int> Params;
        Params.push_back( TypeToInt<P0>() );
        Params.push_back( TypeToInt<P1>() );
    }
    
    // PARSER GENERATED CALLS
    BindClassCtor<Foo, int, int>(&fake_ctor);
    BindClassCtor<Foo, int, std::string>(&fake_ctor);
    

    Implementing the fake_ctor to actually invoke the ctor (even though fake_ctor itself will never be called) provides a level of compile time sanity. If Foo changes one of the constructors without regenerating the BindClassCtor calls, it will likely result in a compile time error.

    Edit: As a bonus, I simplified parameter binding using templates with variadic arguments. First, the BindParams template:

    template <typename... T> struct BindParams;
    
    template <typename T, typename P0, typename... PN>
    struct BindParams<T, P0, PN...> {
        void operator () (std::vector<int> &Params) {
            Params.push_back( TypeToInt<P0>() );
            BindParams<T, PN...>()(Params);
        }
    };
    
    template <typename T>
    struct BindParams<T> {
        void operator () (std::vector<int> &Params) {}
    };
    

    Now, fake_ctor is now a collection of classes, so that each can be instantiated by a variadic parameter list:

    template <typename... T> struct fake_ctor;
    
    template <typename T>
    class fake_ctor<T> {
        static T * x (T *mem) {
            return mem ? new (mem) T() : 0;
        }
        decltype(&x) y;
    public:
        fake_ctor () : y(x) {}
    };
    
    template <typename T, typename P0>
    class fake_ctor<T, P0> {
        static T * x (T *mem, P0 p0) {
            return mem ? new (mem) T(p0) : 0;
        }
        decltype(&x) y;
    public:
        fake_ctor () : y(x) {}
    };
    
    template <typename T, typename P0, typename P1>
    class fake_ctor<T, P0, P1> {
        static T * x (T *mem, P0 p0, P1 p1) {
            return mem ? new (mem) T(p0, p1) : 0;
        }
        decltype(&x) y;
    public:
        fake_ctor () : y(x) {}
    };
    

    And now the binder function is simply this:

    template <typename... T>
    void BindClassCtor (fake_ctor<T...>) {
        std::vector<int> Params;
        BindParams<T...>()(Params);
    }
    

    Below is an illustration of the constructor argument bindings for Bar that has four constructors.

    struct Bar {
        Bar () {}
        Bar (int) {}
        Bar (int, int) {}
        Bar (int, std::string) {}
    };
    
    BindClassCtor(fake_ctor<Bar>());
    BindClassCtor(fake_ctor<Bar, int>());
    BindClassCtor(fake_ctor<Bar, int, int>());
    BindClassCtor(fake_ctor<Bar, int, std::string>());