4

I have a method in a baseclass that needs the type passed to it for some type-related operations (lookup, size, and some method invocation). Currently it looks like this:

class base
{
    template<typename T>
    void BindType( T * t ); // do something with the type
};

class derived : public base
{
    void foo() { do_some_work BindType( this ); } 
};

class derivedOther : public base 
{
    void bar() { do_different_work... BindType( this ); } 
};

However, I wonder if there's a way to get the caller's type without having to pass this so that my callpoint code becomes:

class derived : public base
{
  void foo() { BindType(); } 
};

Without the explicit this pointer. I know that I could supply the template parameters explicitly as BindType<derived>(), but is there a way to somehow extract the type of the caller in some other way?

Steven
  • 619
  • 5
  • 24
  • This is a typical use case for the CRTP http://stackoverflow.com/questions/4173254/what-is-the-curiously-recurring-template-pattern-crtp – Vinzenz Oct 21 '16 at 18:57
  • use `typeid(*this)` in the base class? – davmac Oct 21 '16 at 19:11
  • @davmac typeid doesn't give me the type in a way I can use to .., say call sizeof(T) or T::SomeFunction() - it only gives me the typeid. – Steven Oct 21 '16 at 19:12
  • @Steven right, but your question says only that you need it for "some type-to-index lookup code" and for that purpose it should be fine. – davmac Oct 21 '16 at 19:14
  • @davmac fair enough - I will update the question. – Steven Oct 21 '16 at 19:15
  • @skypjack Interesting - I didn't realize that we can now templatize the constructor. This kind-of works. – Steven Oct 21 '16 at 23:17
  • @Steven Added an answer with all the details. Let me know if it's clear. – skypjack Oct 22 '16 at 07:44

4 Answers4

4

There's no magical way to get the caller's type, but you can use CRTP (as a comment mentions) in order to automate this behavior, at the cost of a bit of code complexity:

class base
{
    template<typename T>
    void BindType(); // do something with the type
};

template <class T>
class crtper : base
{
     void BindDerived { BindType<T>(); }
}

class derived : public crtper<derived>
{
    void foo() { do_some_work BindDerived(); } 
};

class derivedOther : public crtper<derivedOther>
{
    void bar() { do_different_work... BindDerived(); } 
};

Edit: I should mention, I would kind have expected that foo would be a virtual function, defined without implementation in base. That way you would be able to trigger the action directly from the interface. Although maybe you have that in your real code, but not in your example. In any case, this solution is perfectly compatible with this.

Edit2: After question edit, edited to clarify that solution still applies.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • I don't think my sample was clear -- foo is simple a method defined in the derived class specific to the derived class; the use of BindType there isn't consistent across all derived classes of base. – Steven Oct 21 '16 at 19:09
  • @Steven My solution still works, just need to change the names of things. See updated code. – Nir Friedman Oct 21 '16 at 19:15
3

If you want to avoid BindType<derived>(), consider (a bit verbose, I agree) BindType<std::remove_reference<decltype(*this)>::type>(); to avoid passing a parameter. It gets resolved at compile-time and avoids run-time penalties.

class base
{
protected:
    template<typename T>
    void BindType() { cout << typeid(T).name() << endl; } // do something with the type
};

class derived : public base
{
public:
    void foo()
    {
        BindType<std::remove_reference<decltype(*this)>::type>();
    }
};
AlexD
  • 32,156
  • 3
  • 71
  • 65
  • 2
    I don't see the advantage of this over just explicitly specifying `derived` in the angle brackets (as OP mentioned), unless you are planning to use a macro or copy and paste. – Nir Friedman Oct 21 '16 at 19:05
  • @NirFriedman I think agree - this is more wordy. I suspect most compilers would 'do the right thing' and optimize it away. – Steven Oct 21 '16 at 19:08
  • @Steven Well, my concern is not related to the performance of the code. In all likelihood, the OP's code is also very likely to get fully optimized anyhow (a function called once usually gets inlined, and once the function is inlined it's game over). I'm really only thinking about code clarity. – Nir Friedman Oct 21 '16 at 19:11
  • @NirFriedman I see no advantage if the type name (`derived`) is easily accessible in the given context. – AlexD Oct 21 '16 at 19:11
  • `decltype(*this)` is `derived&`, not `derived`. – skypjack Oct 21 '16 at 19:35
  • @skypjack Thanks! Updated. – AlexD Oct 21 '16 at 19:47
2

It will not work as you expect

The result of foo() might be different of what you expect:

class derived : public base           // <= YOU ARE IN CLASS DERIVED
{
public:
    void foo() { BindType( this ); }  // <= SO this IS OF TYPE POINTER TO DERIVED
};

The template paramter is deducted at compile time, so that it will be derived*. If you would call foo() from a class derived_once_more derived from derived, it would still use the type derived*.

Online demo

But you can get rid of the dummy parameter*

You may use decltype(this) to represent the typename of a variable. It's still defined at compile time:

class base
{
public: 
    template<typename T>
    void BindType( ) 
    { 
         cout << typeid(T*).name()<<endl;  // just to show the type 
    }
    virtual ~base() {};                    // for typeid() to work 
};
class derived : public base
{
public: 
    void foo() { BindType<decltype(this)>( ); } 
};

Online demo

Edit: other alternatives

As template parameters need to be provided at compile-time and not a run time, you can use:

  • template parameter deduction (your first code snippet)
  • decltype (see above)
  • if you intend to add this in all the derived classes you could use a macro to get it done, using one of the above mentionned solution
  • you could use the CRTP pattern (already explained in another answer).
Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Yes - I agree that the type of this is specific to the calling context, even if called from a derived class. I think that's okay as I am mostly worried about having greater type context than base. It's a fair criticism. the decltype(this) certainly removes the dummy parameter, but it requires more typing at the callpoint. – Steven Oct 21 '16 at 19:20
  • 1
    As much as all of the examples and solutions provided are interesting, I am actually going to go with my original solution! BindToType(this) is easy to read and doesn't require people who are new to modern C++ to understand what decltype does for us. Christophe's solution covers some of the issues and solutions, so I think it's the best choice for a good answer. Thanks all. – Steven Oct 23 '16 at 00:03
2

A possible solution that avoids the intermediate class of the CRTP follows:

class base {
    using func_t = void(*)(void *);

    template<typename T>
    static void proto(void *ptr) {
        T *t = static_cast<T*>(ptr);
        (void)t;
        // do whatever you want...
    }

protected:
    inline void bindType() {
        func(this);
    }

public:
    template<typename T>
    base(T *): func{&proto<T>} {}

private:
    func_t func;
};

struct derived1: base {
    derived1(): base{this} {}

    void foo() {
         // ...
        bindType();
    }
};

struct derived2: base {
    derived2(): base{this} {}

    void bar() {
        // ...
        bindType();
    }
};

int main() {
    derived1 d1;
    d1.foo();

    derived2 d2;
    d2.bar();
}

The basic idea is to exploit the fact that the this pointers in the constructor of the derived classes are of the desired types.
They can be passed as a parameter of the constructor of the base class and used to specialize a function template that do the dirty job behind the hood.
The type of the derived class is actually erased in the base class once the constructor returns. Anyway, the specialization of proto contains that information and it can cast the this pointer of the base class to the right type.

This works fine as long as there are few functions to be specialized.
In this case there is only one function, so it applies to the problem pretty well.


You can add a static_assert to add a constraint on T, as an example:

template<typename T>
base(T *t): func{&proto<T>} {
    static_assert(std::is_base_of<base, T>::value, "!");
}

It requires to include the <type_traits> header.

skypjack
  • 49,335
  • 19
  • 95
  • 187