推断这个'从派生类调用时的指针类型?

时间:2016-10-21 18:53:59

标签: c++ templates types type-deduction

我在基类中有一个方法,它需要传递给它的类型用于某些与类型相关的操作(查找,大小和一些方法调用)。目前它看起来像这样:

event = soup.find('td', {'id': "highlight_today"})
det = event.select_one("span.econoitems")
name, an = det.text, det.a["href"]
print(name, an )

但是,我想知道是否有一种获取调用者类型的方法,而不必传递它,以便我的调用点代码变为:

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 ); } 
};

没有显式的this指针。我知道我可以明确地将模板参数提供为class derived : public base { void foo() { BindType(); } }; ,但有没有办法以某种方式以某种方式提取调用者的类型?

4 个答案:

答案 0 :(得分:4)

获取调用者的类型没有神奇的方法,但您可以使用CRTP(作为注释提及)以自动执行此行为,代价是代码复杂性:

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(); } 
};

编辑:我应该提一下,我希望foo是一个虚拟函数,在base中没有实现定义。这样,您就可以直接从界面触发操作。虽然你可能在实际代码中有这个,但在你的例子中没有。无论如何,这个解决方案与此完全兼容。

编辑2:编辑问题后,编辑以澄清该解决方案仍然适用。

答案 1 :(得分:3)

如果您想避免BindType<derived>(),请考虑(有点详细,我同意)BindType<std::remove_reference<decltype(*this)>::type>();以避免传递参数。它在编译时得到解决,避免了运行时的惩罚。

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>();
    }
};

答案 2 :(得分:2)

它无法正常工作

foo()的结果可能与您的期望不同:

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

模板参数在编译时时扣除,因此它将为derived*。如果您要从foo()派生的类derived_once_more致电derived,则仍会使用derived*类型。

Online demo

但你可以摆脱虚拟参数*

您可以使用decltype(this)来表示变量的类型名称。它仍然在编译时定义:

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

编辑:其他选择

由于模板参数需要在编译时而不是运行时提供,因此可以使用:

  • 模板参数扣除(您的第一个代码段)
  • decltype (see above)
  • 如果您打算在所有派生类中添加它,您可以使用宏来完成它,使用上面提到的解决方案之一
  • 您可以使用CRTP模式(已在另一个答案中解释)。

答案 3 :(得分:2)

避免CRTP中间类的可能解决方案如下:

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();
}

基本思想是利用派生类的构造函数中的this指针具有所需类型的事实。
它们可以作为基类构造函数的参数传递,并用于专门化一个功能模板,该模板在引擎盖后面执行脏工作
一旦构造函数返回,派生类的类型实际上在基类中被擦除。无论如何,proto的特化包含该信息,它可以将基类的this指针强制转换为正确的类型。

只要几乎没有专门的功能,这种方法就可以正常工作 在这种情况下,只有一个功能,所以它很适用于问题。

您可以添加static_assert以在T上添加约束,例如:

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

它需要包含<type_traits>标题。