从具有通用 return 类型的 crtp 的基 class 派生 class 中调用函数

Calling function in derived class from base class of crtp with generic return type

我正在创建一个允许用户存储指针并稍后检索指针的库。
我有一个 class 存储通用类型的指针。

template <typename generic_type>
class A 
{
public:
    A() {}
    A(generic_type *value) : data(value) {}
    generic_type *getData()
    {
        return data;
    }

private:
    generic_type *data;
};

我还想将这些 class 模板的实例存储在一个向量中,所以我继承了一个基础 class。

class B 
{
};

template <typename generic_type>
class A : public B
{
...
};

class test_class
{
};

std::vector<B *> list;
// -------- example -------
// test_class *test = new test_class();
// list.push_back(new A<test_class>(test));
// list.push_back(new A<test_class>(test));
// list.push_back(new A<test_class>(test));

从列表中检索时,我将得到一个指向基 class.
的指针 要调用派生 class A 中的函数,必须强制转换它。

// example
B *bp = list.at(0); 
A<test_class> *ap = static_cast<A<test_class> *>(bp);

我不希望用户必须自己手动转换基指针,但能够调用 A 中的 getData() 函数,这将 return 实际数据基于在 generic_type.

// preferred API usage
B *bp = list.at(0); // they will get B pointer in another way but shown here for simplicity
test_class *t = bp->getData();

四处寻找从父 class 调用子函数的过程,我发现了 CRTP,它允许我调用 A 中的函数;但现在我无法将其存储在向量中B 将被模板化。所以我从另一个 class 中派生出 B 引用来自这个 post.
的代码 这允许我将它存储在一个向量中,但我似乎无法找到一种方法来调用具有通用 return 类型和 return 的函数。

class C
{
public:
  virtual void func() = 0;
};
template <typename derived>
class B : public C
{
public :
  void func() // cant change to templated return type as virtual function cant be templated
  {
    derived *p = static_cast<derived *>(this);
    p->getData(); // how to return the value from this ?
  }
};

template <typename generic_type>
class A : public B<A<generic_type>>
{
...
};

std::vector<C *> list;
// -------- example -------
// this allows me to do  
C *cp = list.at(0);
cp->func(); // will call getData() indirectly
            // but am unable to get the returned data

我不熟悉更新的功能或设计模式,因为我已经有几年没有使用 c++ 了。 是否可以获取 returned 数据?或者我可以使用不同的方法或模式来实现所需的 API 使用场景吗?

您永远无法回避这样一个事实,即用户必须指定他们希望收到的类型。编译器需要在编译时知道 getData 的 return 类型是什么,所以你必须在编译时决定你正在操作哪个具体的 A 实例。

但是,有一种方法可以使此 "specifying the type" 比手动静态转换更方便:在 B 中提供到任何指针的隐式转换。

class B 
{
public:
    virtual ~B() = default;

    template<class T>
    operator T*();
};

template <typename generic_type>
class A : public B
{
  // ...
};

template<class T>
B::operator T*()
{
    A<T>* a = dynamic_cast<A<T>*>(this);
    if (!a)
      return nullptr;
    return a->getData();
}

// Usage:

std::vector<std::unique_ptr<B>> list;
list.emplace_back(new A<test_class>);
list.emplace_back(new A<int>);
list.emplace_back((A<double>*)nullptr);

test_class* out1 = *list[0];   // Returns the correct ptr
test_class* out2 = *list[1];   // Returns nullptr (incorrect type)
double* out3 = *list[2];       // Returns nullptr (data value is null)

https://godbolt.org/z/NT1AF8

此转换运算符假定,当我们尝试将某些对象 xB 转换为某些 T* 时,x 的动态类型是 A<T*>。它尝试向下转换,如果成功则 returns getData(),否则 nullptr

这在原则上工作得很好,但是您应该意识到隐式转换最好谨慎使用或根本不使用 - 它们会导致非常令人惊讶的行为。您可能还想 (SFINAE-) 防止有人创建 A<A<something>>A<B>,因为这听起来很头疼。

让它与 cv 说明符(即 const/volatile)一起工作将需要更多的工作(包括指定 A<const int> 是否应该是一个东西)。


你也可以在手动 static_cast 和上面的隐式转换之间采取中间路线,方法是编写一个自由访问函数,如

template<class T>
T* getData(B& b)
{
  // See operator T* implementation above.
}

这将像 double* y = getData<double>(x); 一样使用。对于库界面,我更喜欢这种解决方案。