Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to store type information, gathered from a constructor, at the class level to use in casting

I am trying to write a class that I can store and use type information in without the need for a template parameter.

I want to write something like this:

class Example
{
public:
    template<typename T>
    Example(T* ptr)
        : ptr(ptr)
    {
        // typedef T EnclosedType; I want this be a avaialable at the class level.
    }

    void operator()()
    {
        if(ptr == NULL)
            return;

        (*(EnclosedType*)ptr)(); // so i can cast the pointer and call the () operator if the class has one.
    }

private:
    void* ptr;
}

I am not asking how to write an is_functor() class.

I want to know how to get type information in a constructor and store it at the class level. If that is impossible, a different solution to this would be appreciated.

like image 940
rationalcoder Avatar asked Jun 02 '15 21:06

rationalcoder


3 Answers

I consider this as a good and valid question, however, there is no general solution beside using a template parameter at the class level. What you tried to achieve in your question -- using a typedef inside a function and then access this in the whole class -- is not possible.

Type erasure

Only if you impose certain restrictions onto your constructor parameters, there are some alternatives. In this respect, here is an example of type erasure where the operator() of some given object is stored inside a std::function<void()> variable.

struct A
{
    template<typename T>
    A(T const& t) : f (std::bind(&T::operator(), t)) {}

    void operator()() const
    {
        f();
    }

    std::function<void()> f;
};

struct B
{
    void operator()() const
    {
        std::cout<<"hello"<<std::endl;
    }
};

int main()
{
    A(B{}).operator()();  //prints "hello"
}

DEMO

Note, however, the assumptions underlying this approach: one assumes that all passed objects have an operator of a given signature (here void operator()) which is stored inside a std::function<void()> (with respect to storing the member-function, see here).

Inheritance

In a sense, type erasure is thus like "inheriting without a base class" -- one could instead use a common base class for all constructor parameter classes with a virtual bracket operator, and then pass a base class pointer to your constructor.

struct A_parameter_base
{
    void operator()() const = 0;
};

struct B : public A_parameter_base
{
    void operator()() const { std::cout<<"hello"<<std::endl; }           
};

struct A
{
    A(std::shared_ptr<A_parameter_base> _p) : p(_p) {}

    void operator()()
    {
        p->operator();
    }

    std::shared_ptr<A_parameter_base> p;
}

That is similar to the code in your question, only that it does not use a void-pointer but a pointer to a specific base class.

Both approaches, type erasure and inheritance, are similar in their applications, but type erasure might be more convenient as one gets rid of a common base class. However, the inheritance approach has the further advantage that you can restore the original object via multiple dispatch

This also shows the limitations of both approaches. If your operator would not be void but instead would return some unknown varying type, you cannot use the above approach but have to use templates. The inheritance parallel is: you cannot have a virtual function template.

like image 126
davidhigh Avatar answered Oct 22 '22 23:10

davidhigh


The practical answer is to store either a copy of your class, or a std::ref wrapped pseudo-reference to your class, in a std::function<void()>.

std::function type erases things it stores down to 3 concepts: copy, destroy and invoke with a fixed signature. (also, cast-back-to-original-type and typeid, more obscurely)

What it does is it remembers, at construction, how to do these operations to the passed in type, and stores a copy in a way it can perform those operations on it, then forgets everything else about the type.

You cannot remember everything about a type this way. But almost any operation with a fixed signature, or which can be intermediaried via a fixed signature operation, can be type erased down to.

The first typical way to do this are to create a private pure interface with those operations, then create a template implementation (templated on the type passed to the ctor) that implements each operation for that particular type. The class that does the type erasure then stores a (smart) pointer to the private interface, and forwards its public operations to it.

A second typical way is to store a void*, or a buffer of char, and a set of pointers to functions that implement the operations. The pointers to functions can be either stored locally in the type erasing class, or stored in a helper struct that is created statically for each type erased, and a pointer to the helper struct is stored in the type erasing class. The first way to store the function pointers is like C-style object properties: the second is like a manual vtable.

In any case, the function pointers usually take one (or more) void* and know how to cast them back to the right type. They are created in the ctor that knows the type, either as instances of a template function, or as local stateless lambdas, or the same indirectly.

You could even do a hybrid of the two: static pimpl instance pointers taking a void* or whatever.

Often using std::function is enough, manually writing type erasure is hard to get right compared to using std::function.

like image 42
Yakk - Adam Nevraumont Avatar answered Oct 22 '22 21:10

Yakk - Adam Nevraumont


Another version to the first two answers we have here - that's closer to your current code:

class A{
public:
  virtual void operator()=0;
};

template<class T>
class B: public A{
public:
  B(T*t):ptr(t){}
  virtual void operator(){(*ptr)();}
  T*ptr;
};

class Example
{
public:
    template<typename T>
    Example(T* ptr)
    : a(new B<T>(ptr))
    {
        // typedef T EnclosedType; I want this be a avaialable at the class level.
    }

    void operator()()
    {
        if(!a)
            return;

        (*a)();
    }

private:
    std::unique_ptr<A> a;
}
like image 41
rabensky Avatar answered Oct 22 '22 23:10

rabensky