【问题标题】:Pass object with derived template to function that accepts object with base template将具有派生模板的对象传递给接受具有基本模板的对象的函数
【发布时间】:2020-10-28 00:29:48
【问题描述】:

如何将具有派生模板实例化的对象传递给接受具有基本模板实例化的那些对象的方法?

这似乎是可能的,因为 std::shared_ptr 或 std::pair 似乎有能力做到这一点。

例如

#pragma once

#include <iostream>
#include <memory>

struct Base {
    virtual void print() = 0;
};

struct Derived : public Base {
    void print() {
        std::cout << "Got it!" << std::endl;
    }
};

void printBase(const std::shared_ptr<Base> &ptr){
    ptr->print();
}

void printBase(const std::pair<Base&, Base&> &pr){
    pr.first.print();
}

template <typename T>
struct Wrap {
    T& t;
};

void printBase(const Wrap<Base> &wrap) {
    wrap.t.print();
}

int main() {
    Derived d;
    std::shared_ptr<Derived> ptr = std::make_shared<Derived>(d);
    printBase(ptr); // works
    std::pair<Derived&, Derived&> pr = {d, d};
    printBase(pr); // works
    Wrap<Derived> w = Wrap<Derived>{d};
    // printBase(w); // gives compile error
}

【问题讨论】:

    标签: c++ templates inheritance parameter-passing


    【解决方案1】:

    您需要将转换构造函数和/或赋值运算符显式添加到您的 Wrapped 类型,以便能够从不同类型进行转换。

    std::shared_ptrstd::pair 在内部都是这样做的; shared_ptr&lt;T&gt; 可以从shared_ptr&lt;U&gt; 类型构造(具有U* 可转换为T* 的SFINAE 限制),并且pair&lt;T,U&gt; 可以从pair&lt;T2,U2&gt; 类型构造(具有SFINAE 限制T2 可转换为TU2 可转换为 U)。

    这样做就像添加一个新的构造函数一样简单:

    template <typename T>
    struct Wrap
    {
        Wrap(T& ref)
            : t{ref}
        {
        }
    
        template <typename U, typename = std::enable_if_t<std::is_convertible_v<U&, T&>>>
        Wrap(const Wrap<U>& other)
            : t{other.t}
        {
        }
    
        T& t;
    };
    

    上面的示例使用is_convertible 作为enable_if 的条件,因此只有当U 的引用可以转换为T 的引用时,构造函数才可见。这将限制它,使得U 必须与T 分层相关(因为引用不可转换)——这将允许Wrapped&lt;Derived&gt; 转换为Wrapped&lt;Base&gt;,但反之则不行。


    编辑: 正如 cmets 中提到的,值得注意的是,与属于层次结构的类型不同——其中对 Derived 的引用可以作为对 Base 的引用传递,包装层次结构的类型将无法对aTemplate&lt;Derived&gt;的引用作为对aTemplate&lt;Base&gt;的引用。 p>

    std::shared_ptr&lt;Derived&gt; 传递给const std::shared_ptr&lt;Base&gt;&amp; 的示例仅由于C++ 中的const-lifetime 扩展而实际工作。这实际上并没有将它作为引用传递——而是具体化了一个 std::shared_ptr&lt;Base&gt; 的临时对象,该对象被传递给引用。它实际上与按值传递相同。

    这也意味着您不能Template&lt;Derived&gt; 传递给非const Template&lt;Base&gt; 引用,因为只有const 引用才会延长生命周期。


    编辑:如 cmets 中所述:

    不需要使构造函数进行复制转换;它可以很容易地成为一个 R 值构造函数。但是,如果您正在对包装类型的销毁进行清理,那么您将需要以某种方式标记不需要清理已移动对象。最简单的方法是使用指针,移动后重新绑定到nullptr

    template <typename T>
    struct Wrap
    {
        Wrap(T& ref)
            : t{std::addressof(ref)}
        {
        }
    
        Wrap(Wrap&& other) 
            : t{other.t}
        {
            other.t = nullptr;
        }
    
        Wrap(const Wrap&) = delete;
    
        template <typename U, typename = std::enable_if_t<std::is_convertible_v<U&, T&>>>
        Wrap(Wrap<U>&& other)
            : t{other.t}
        {
            other.t = nullptr;
        }
    
        ~Wrap() {
          if (t != nullptr) {
              cleanup(*t); // some cleanup code
          }
        }
    
        T* t;
    };
    

    如果您想要的 API 无法使用指针,那么您可能需要使用在移动期间适当设置的 bool needs_cleanup,因为无法反弹引用。

    注意:如果数据是private 而不是本例中的public,您可能需要有一个friend 声明:

    template <typename> friend class Wrap;
    

    这样Wrap&lt;T&gt; 就可以访问Wrap&lt;U&gt; 的私​​有数据成员。

    【讨论】:

    • 这都是真的,但它只适用于传值。如果类型是 const 引用,这将创建一个临时对象并传递它,因此与按值传递没有太大区别。非常量引用已失效。
    • @n.'pronouns'm。我不确定你的意思是什么。 shared_ptrpair 也是如此,这个问题明确提到了它在基/派生类型之间的隐式转换
    • 是的,当然智能指针也是如此。 OP 显示了一个接受 const 引用的函数。我只是指出潜在的问题。
    • 啊,我明白你的意思了。我可能会为此添加一个脚注,以防该部分不清楚。
    • @n.'pronouns'm。更新。感谢您的意见!
    【解决方案2】:

    printBase函数改成如下:

    template <typename T, typename = std::enable_if<std::is_base_of_v<Base, T>>>
    void printBase(const Wrap<T> &wrap){
        wrap.t.print();
    }
    

    【讨论】:

    • @cdhowie 你说得对,是我的错,看看编辑后的版本。
    • @cdhowie 我错误地从 Wrap 成员变量 t 中删除了 &amp;,它抛出了 const 编译异常。所以是的,您不必创建print 函数const
    • 如果您需要 printBase 作为虚拟成员函数,这将不起作用。
    • @n.'pronouns'm。你能举个例子吗,我不确定我是否完全理解你。
    猜你喜欢
    • 1970-01-01
    • 2017-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-29
    • 1970-01-01
    • 2011-11-22
    相关资源
    最近更新 更多