将具有派生模板的对象传递给接受具有基本模板的对象的函数

Pass object with derived template to function that accepts object with base template

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

这似乎是可能的,因为 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
}

printBase 函数更改为以下函数:

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

您需要显式地向 Wrapped 类型添加转换构造函数 and/or 赋值运算符,以便能够从不同类型进行转换。

这就是 std::shared_ptrstd::pair 内部执行此操作的方式; shared_ptr<T> 可以从 shared_ptr<U> 类型构造(SFINAE 限制 U* 可以转换为 T*),并且 pair<T,U> 可以从 pair<T2,U2> 构造类型(根据 SFINAE 限制,T2 可转换为 T,而 U2 可转换为 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<Derived> 转换为 Wrapped<Base> , 但反之则不然。


编辑: 如评论中所述,值得注意的是,与属于层次结构的类型不同——可以传递对 Derived 的引用作为对 Base 的引用,包装层次结构 的类型将不会 能够将 引用传递给 Template<Derived> 作为引用 Template<Base>.

带有 std::shared_ptr<Derived> 的示例被传递给 const std::shared_ptr<Base>& 只是因为 const-C++ 中的生命周期延长才真正起作用。这实际上并没有将它作为引用传递——而是具体化了一个 std::shared_ptr<Base> 的临时对象,该对象被传递给了引用。它实际上与按值传递相同。

这也意味着您 不能Template<Derived> 传递给非 const Template<Base> 引用,因为只能延长生命周期发生在 const 个引用中。


编辑: 如评论中所述:

构造函数不需要复制转换;它很容易成为 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<T> 可以访问 Wrap<U> 的私有数据成员。