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

Hol*_*ann 3 c++ inheritance templates parameter-passing

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

似乎有可能,因为 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
}
Run Code Online (Sandbox Code Playgroud)

Hum*_*ler 6

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

这是怎样既std::shared_ptrstd::pair内部做到这一点; shared_ptr<T>可以从shared_ptr<U>类型构造(具有U*可转换为 的SFINAE 限制T*),pair<T,U>也可以从pair<T2,U2>类型构造(具有T2可转换为TU2可转换为 的SFINAE 限制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;
};
Run Code Online (Sandbox Code Playgroud)

上面的例子is_convertible用作条件 forenable_if所以构造函数只有在U可以将 的引用转换为 的引用时才可见T。这将限制它U必须与层次相关T(因为引用不能以其他方式转换)——这将允许 aWrapped<Derived>转换为Wrapped<Base>,但反之亦然。


编辑:正如评论中提到的,值得注意的是,与属于层次结构的类型不同——其中的引用Derived可以作为对 的引用传递Base,包装层次结构的类型将无法传递 Template<Derived>as的引用对 a 的引用 Template<Base>

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

这也意味着您不能将 aTemplate<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;
};
Run Code Online (Sandbox Code Playgroud)

如果您所需的 API 无法使用指针,则您可能需要使用bool needs_cleanup在移动期间适当设置的a ,因为无法重新绑定引用。

注意:如果数据是,private而不是public在这个例子中,你可能需要friend声明:

template <typename> friend class Wrap;
Run Code Online (Sandbox Code Playgroud)

这样 a 就Wrap<T>可以访问 a 的私有数据成员Wrap<U>