Friday, November 12, 2010

Currently Lua and Python are in the works, because Lua's API is different from what I'm used to, and Python is well, not fun to embed but simple enough. Conceptually there is no major difference between a parameter list and a stack, it's just a sequence of data at heart, and Python functions basically use sequence objects.

What would be awesome, is if the calls are defined in terms of stack manipulation, is to create a template method called Push, that users template specialisation in order to wrangle plain old data types and callables to the right scripting language types, so we would have something like this:


e->Run(SourceString("function f(a, b) print(a); return b * b end")
e->Push(/* instance of some type representing f() */);
e->Push("header message");
e->Push(2);
e->Call();


and rely on the compiler to Get It Right in figuring out the relevant overloads, e.g. Push. Somewhat probmatic is the issuance that Push needs to be part of the abstract base class but can only be defined by a concrete implementation of the interface; normally it would just be defined as a virtual method. That means it works like you would expect a class method to work by default \o/. Only thing with that is templates cannot be virtual, so we would have to sidestep the whole vtable thing.

That's a piece of cake thanks to C++ allowing the abuse of inheritance and casting:


/* An example */

#include <iostream>

template<class Impl> class Base {
  public:
    template <typename T> void Push(T arg)
    {
        static_cast<Impl *>(this)->Push(arg); 
    }
};

// implementation
class Impl : Base<Impl>
{
  public:
    template <typename T> void Push(T arg);

};

template <> void Impl::Push (int arg)
{
    std::cout << arg << std::endl;
}
template <> void Impl::Push (const char *arg)
{
    std::cout << arg << std::endl;
}

// subclass that just overrides one method
class X : Impl  {
  public:
    template <typename T> void Push(T arg);
};

template <> void X::Push (int arg)
{
    std::cout << arg * arg << std::endl;
}
template <> void X::Push (const char *arg)
{
        static_cast<Impl *>(this)->Push(arg); 
}

int main()
{
    {
        Impl test;
        test.Push(2);   // 2
        test.Push("string");
    }
    {
        X test;
        test.Push(2);   // 4
        test.Push("string");
    }
    return 0;
}




Because the parent class is templated against the derived class, it's possible to get jiggy with it at compile time. Namely enough is known by the parent about the child to invoke the correct method. Where it becomes somewhat annoying is when you want to continue with subclasses, like X in the above example.

A year or two ago I learned that people call this the "Continually Reoccurring Template Pattern (CRTP)". Being lazy, I just think of it as the poor mans way of doing something similar to what "virtual template <...> ..." would logically imply, if only the effing compiler was that smart. For what I need, just mating instance method overloading with virtual method calls is good enough.



Now all that is trivial, the real gripe however is how do you properly make a "Convenience" method, let's say one we can do like e->Call(/* variable number of parameters */); and have it do the appropriate magic for us based on the type.


Well, sadly we can't so easily. To use a va_list, there has to be a way to access the type of the argument. Normally this is done the same way that printf() and scanf() work in C, taking a format string saying what type to cast each parameter to. Pythons embedding API actually does this to convert from C data types to Python objects. Someday I need to open up a C library and look at how va_arg() actually works, I've always assumed it's some sort of hack around a block of memory and type casting. It's trivial to implement that kind of thing, already have done it for testing purposes (rather than templated Push()'s) but using a format string to describe the specifiers breaks down on type safety, where as at least with the template thing, the compiler can help some.

We can't rely on Push() overloads to do the right thing because va_arg() is needed to access the arguments if Call() takes a variable number of arguments in the C++ 2003 compliant way. Obviously the easist solution is to find a smarter way of doing va_arg(a_va_list, a_type). Life would be a piece of cake with templates that can take a variable number of parameters, right? Well there's few  vendors out there who seem to know how to do that <_>.

So how the fuck do you do a smart va_arg() like behaviour? The only thing I can think of at the moment is to make them all the same templated type, so it's known how to cast them; then try and work some sort of char_traits<> like magic to figure out which Push() is appropriate but binding the necessary info creates more of the same. That and looking up in the C++ standard how many template parameters (if any) compilers are required to allow, and generating every possible permutation of arguments using a script to make the necessary template code before calling the compiler.

Either way, I'm just taking a break for a few minutes to enjoy how peaceful the quiet has been for the last twenty minutes lol.

2 comments:

  1. Newer C++ compilers (gcc 4.5) support the C++0X standard that allow for variadic templates, which solves some of these problems, which I think you alluded to. Also, you can use default arguments to templates so that you can define e.g. a maximum number of arguments, maybe 8 or so, and let unused arguments default to a harmless type that does nothing.

    ReplyDelete
  2. The problem with C++0x features, is to be useful (to me) this thing will have to compile on compilers lacking variadic templates. So the variadic solution can only be enabled where supported by the users compiler.


    Using default arguments to templates sounds like a good idea for simulating it.


    A special type that can be specialised out as a noop when Push()'d is trivial enough to create. It just has to be a custom class type because using a pointer, enum, or pod integer type, would interfere with passing the numeric value 0 to a scripts function, because of how the compiler infers which overload to use.So that's easy.


    If there are 8 or so arguments with only 1 required: because each argument may be of a different type, template based Push() would be needed, i.e. virtual member functions are no longer viable unless wrapped by templates, which is trivial. I'm sure C++03 says how many arguments a template should minimally allow.


    The only down side I can see, is having to document that if a user needs more than X arguments passed, they will either need the variadic template version or the scanf() style versions of Call().

    Writing a version of Call() able to handle the default arguments, is just a small chore.


    Thanks :-).

    ReplyDelete