2

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.

Sergey K.
  • 24,894
  • 13
  • 106
  • 174
  • 3
    Actually, it's also difficult with regular methods: when they are overloaded. – Matthieu M. Jul 03 '12 at 09:43
  • No problem with overloaded methods here, since you still can take their addresses. – Sergey K. Jul 03 '12 at 09:43
  • 1
    I have a suspicion you're going to be looking at running some tool over the header files to figure this out for you. You can write some template meta programming magic to try and "brute force" the types, but that only works if you have a list of all known types to hand, which is unlikely in any real system. – Flexo Jul 03 '12 at 09:53
  • I have this tool and a list of all known classes/structs in the project. It was made in-house and it generates all the necessary bindings for methods. I'm just planning what to do with constructors. – Sergey K. Jul 03 '12 at 09:59
  • Can you think about delaying this computation until the point when the constructor actually has to be called? `boost::make_shared` to create smart pointer instances is a good example of that strategy. – schroder Jul 22 '12 at 20:09
  • "Maybe there's a fundamentally different approach to solve this" -- solve WHAT? You did not explain what it is that you are trying to achieve, only how you intend to do it. – sellibitze Jul 23 '12 at 09:27
  • @sellibitze: See the title: "Determine the number and type of the class constructor's parameters in C++". – Sergey K. Jul 23 '12 at 09:29
  • @SergeyK. I am aware of the title. But it's probably more interesting to know *why* you want to do this. You're trying to solve some kind of problem that you did not yet explain. – sellibitze Jul 23 '12 at 09:32
  • @sellibitze: i'm developing a scripting system and would like to instantiate classes which have non-default constructors from script. – Sergey K. Jul 23 '12 at 09:55
  • @SergeyK. first of all please clarify if you want to achieve your goal during runtime (what I assume) or not. Secondly, your example does not have ANYTHING to do with figuring the number of parameters in a member (or not) function. It is just an overload. Finally, that is impossible to do in runtime, because one can pass arbitrary number of arguments to a function (just proper typecasting is needed to satisfy the compiler). – friendzis Jul 24 '12 at 08:51
  • @friendzis: i want to have it done before compilation, i have a preprocessing tool that knows all the classes and their methods (and constructors) and i want to know what code should it generate so i can invoke the constructors properly. – Sergey K. Jul 24 '12 at 08:59
  • How about using boost function traits? They already solved this for you http://stackoverflow.com/a/2165051/227755 – nurettin Jul 24 '12 at 11:07
  • @pwned: How it can be applied to constructors? – Sergey K. Jul 24 '12 at 11:21
  • 1
    @SergeyK. I'm sorry I don't think that is possible due to the standard, you will have to make a proxy factory method. – nurettin Jul 24 '12 at 11:39
  • @pwned: Surely. The question is - what is the best way to do it with autogenerated code? – Sergey K. Jul 24 '12 at 11:39
  • Maybe I'm just being stupid. But isn't the constructor for MyClass located at &MyClass::MyClass(int p1, float p2) just like any other member function? – std''OrgnlDave Jul 24 '12 at 13:00
  • @std''OrgnlDave: yes, except you cannot take the address of the constructor - in C++ there is no type for that. But you can wrap a ctor in a static function and take its address. – Sergey K. Jul 24 '12 at 13:07
  • There's a workaround. Sortof. Annoying template specialization. Specialize a template for every class you want to do this with. – std''OrgnlDave Jul 24 '12 at 20:04
  • How? Could you please elaborate on this? – Sergey K. Jul 24 '12 at 20:14
  • It looks like you are trying to make bindings for a scripting language to call C++. http://www.swig.org is quite capable of generating wrappers, both for the C++ side & the scripting side. We have it integrated into our build system, so any changes that happen in our C++ libraries automatically propagate to the script bindings. – Zac Jul 27 '12 at 16:40

2 Answers2

6

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>());
jxh
  • 69,070
  • 8
  • 110
  • 193
  • Yep, this looks exactly like what i need. – Sergey K. Jul 25 '12 at 09:44
  • This is a perfect trick for run-time. The question is about compile-time however. Good direction anyways, +1. Looks like we'll just have to generate everything, as we always thought. – Viktor Latypov Jul 25 '12 at 19:24
  • @ViktorLatypov: As I understood it, Sergey has a script that walks his source code and generates calls to his `BindNativeMethod` whenever he encounters a class method. He wanted to do something similar for the class constructors. So his script would generate calls to `BindClassCtor`, but instead of passing in method pointers, it would pass in the address to the `fake_ctor`. – jxh Jul 25 '12 at 19:33
  • You understand everything correctly, our source processor (SWIG-like, actually) parses C++ headers and stores the "binders" for each encountered method. The binder has an Invoke(vector) interface and we want the same for constructors. Our template machinery can determine how to cast these 'void*' to P0, P1 etc., but we have to now the P0..P_N for constructors. Looks like you are close to the solution. – Viktor Latypov Jul 25 '12 at 19:36
  • @ViktorLatypov: I modified the answer to reflect how I imagine the generated calls would look like. Regards – jxh Jul 25 '12 at 20:12
1

I would suggest a workaround for this problem. I often use a "static instance constructor" pattern - for example for deserializing instance of a class, which is subclassed, eg.

class BaseClass
{
private:
    BaseClass();
    virtual void InternalLoad(MyStream s) = 0;

public:
    static BaseClass * Create(MyStream s);
};

(...)

BaseClass * BaseClass::Create(MyStream s);
{
    int type;
    s >> type;

    if (type == 0)
    {
        BaseClass * result = new DerivedClass1(); // DerivedClass1 : public BaseClass
        result->InternalLoad(s);
        return result;
    }
    else if (type == 1)
    ...
}

You may easily use this pattern in your case. Just create a static instance constructor, which is generic and processes its parameters in a way you want it to.


Edit:

I think, that you can solve the problem in the following way:

class MyClass
{
private:
    MyClass();

public:
    template <typename T1, typename T2>
    static MyClass * Create(T1 t, T2 u);
};

(...)

template <typename T1, typename T2>
MyClass * MyClass::Create(T1 t, T2 u)
{
    MyClass result = new MyClass();
    // Process parameters t and u here
    return result;
}

Then you may construct the class in the following way:

MyClass* class = MyClass::Create(5, "Indiana Jones");

Please notice, that it is not a solution, but (quite elegant, I think) workaround.


Edit:

I think, that I understand now, what you're trying to do and using static method instead of constructor actually may still prove as a workaround for your problem (but that depends on what do you want to do with the information about the constructor parameters).

Obviously, you should then implement the static constructor with regular parameters, as:

class MyClass
{
private:
    MyClass();

public:
    static MyClass * Create(int t, char * u);
};

You should be able to retreive a pointer to static function and pass it to your tool methods.


Edit:

Since you explained, what do you really want to do, I would suggest using class factories (preferred) or static constructors, as proposed earlier. This way you can always retrieve a pointer to needed method (either static or not) and use it in your current architecture (basing on the code you've presented).

Also, take a look on the std::function<> and lambdas in C#/11. I guess, that they may make your architecture a lot simpler.

Sergey K.
  • 24,894
  • 13
  • 106
  • 174
Spook
  • 25,318
  • 18
  • 90
  • 167
  • What about constructor parameters? – Sergey K. Jul 23 '12 at 06:12
  • Maybe I misunderstood your question. As far as I understand, you want to create a generic version of class constructor, right? – Spook Jul 23 '12 at 06:38
  • Not exactly. I would like to create instances of the classes from script. This is solved for classes which have the default constructor. However, I also need to work with classes that don't have a default constructor. – Sergey K. Jul 23 '12 at 09:57
  • no real answer to the problem was given and no answer to the comments either. Will remain downvoted until something relevant is provided. – Sergey K. Jul 24 '12 at 11:26
  • Actually the last edit of my post provides an answer for your last comment. Have you read my whole post with all edits? You didn't commented them at all. – Spook Jul 24 '12 at 11:30
  • Yes, i did! Unfortunately static constructors cannot be used in the way you described. This code will be autogenerated and should go into separate files. There is no way for me to safely modify the class body (partial classes in C# are great, i know). – Sergey K. Jul 24 '12 at 11:32
  • But you can make the class factorys with methods, which will create the apropriate classes. These can be easily autogenerated for each class you export. That would require a change in your architecture, but it would solve the problem. Note, that class factorys can be instantiated and then used. – Spook Jul 24 '12 at 11:34
  • What changes in the architecture do you mean? – Sergey K. Jul 24 '12 at 11:38
  • In all places you currently use constructors, you would have to use a class factory instead (including script definitions, I guess). You've shown very little of your code, so I have to speculate. But I still think, that class factory would be an elegant workaround for your problem. – Spook Jul 24 '12 at 11:42
  • I already do it this way: Env->Linker->Instantiate("clMyClassName"); I guess i will just need to add overloads for the instantiation, like i have for invoking ordinary functions by name. – Sergey K. Jul 24 '12 at 11:45
  • Or use a class factory and do it in the following way: auto c = Env->Linker->Instantiate("clsFactory"); Env->Linker->Call(c, "CreateClass", 1, 2, "sth"); (or however you call your regular methods). – Spook Jul 24 '12 at 11:49