静态方法中派生类的类型演绎

C++11, type deduction of derived class in static method

本文关键字:类型 演绎 派生 静态方法      更新时间:2023-10-16

最小示例:

class Task
{
public:
  template<typename T, typename... Args>
  static T* make(Args... args) {
    return new T(args...);
  }
}
template<typename A, typename B, typename C>
class MyTask : public Task
{
public:
  MyTask(A a, B b, C c)
  {
  }
}

make工厂方法的存在是为了使我在模板化的派生类实例化时不必提供所有的模板类型。

我希望能够创建一个实例的MyTask尽可能简洁,即:

auto my_task = MyTask::make(a, b, c);

但是,编译器坚持认为它不能推导出T,而想要:

auto my_task = MyTask::make<MyTask<A, B, C>>(a, b, c);

这不是什么大问题,但重复似乎没有必要。有没有办法让它变成我想要的样子?

问题是MyTask::make不是真的,而是Task::make。在调用中使用的名称是查找的起点,而不是函数名称。所以严格来说没有重复。

您可以做不同的事情,例如,使用CRTP(正如Massa在评论中建议的那样)让派生类型将其类型注入到基础中-以不同基础的不同类型为代价,如果您有一些实际的接口,您将需要提供CRTP作为TaskMyTask之间的中间helper类型,或者更好地作为外部CRTP helper

template <typename T>
struct Maker {
    template <typename ...Args>
    static T *make(Args ... args) { return new T(args...); }
};
class MyTask : public Maker<MyTask>, public Task { ... };
auto task = MyTask::make(a,b,c);

另一种方法是使函数成为一个自由函数,只传递你想要构建的类型:

template <typename T, typename ...Args>
T* make(Args ... args);
auto x = make<MyTask>(a,b,c); // only named once here
为了支持上面模板代码中的nice语法,而不必提供模板参数,您可以根据模板而不是类型来实现make:
template <template <typename...> class T,
          typename ... Args>
T<Args...>* make(Args... args) {
    return new T<Args...>(args...);
}

你的问题没有意义,有几个不同的原因——make()Task的成员,但你谈论的是MyTask::make();你打算让MyTaskTask衍生出来吗?同时,这里

auto my_task = MyTask::make<MyTask>(a, b, c);
//                  ^^^          ^^^

显然,不指定模板参数就不能使用MyTask

我想你想要演示的是这个(我添加了完美转发):

template<typename T>
class Task
{
public:
  template<typename... Args>
  static T* make(Args&&... args) {
    return new T(std::forward<Args>(args)...);
  }
};
template<typename A, typename B, typename C>
class MyTask : public Task<MyTask<A, B, C>>
{
    public: MyTask(A, B, C) {}
};

你会用

auto my_task = MyTask<int, long, double>::make(10, 20L, 30.);

非常冗长。这可以通过创建一个委托给Task::make()的包装器函数来避免(或者去掉中间人,如果可行的话,在包装器本身内完成Task::make中完成的工作)

template<typename A, typename B, typename C>
MyTask<A, B, C> *make_MyTask(A&& a, B&& b, C&& c)
{
    return Task<MyTask<A, B, C>>::make(std::forward<A>(a), 
                                       std::forward<B>(b), 
                                       std::forward<C>(c));
}

预期用途:

auto my_task = make_MyTask(10, 20L, 30.);

现场演示

另一个建议是您更改make()函数以返回unique_ptr<...>而不是原始指针。

辅助函数将是有价值的;模板实参推导可以通过它

using namespace std;
template<typename T>
class Task
{
public:
    template<typename... Args>
    static T* make(Args... args) 
    {
        return new T(args...);
    }
};
// I find it easier to inherit from a class that actually knows what type to  
// return in the "make" function
template<typename A, typename B, typename C>
class MyTask : public Task<MyTask<A, B, C>>
{
public:
    MyTask(A a, B b, C c) { }
};
// basically this is the function, whose template argument deduction
// eliminates the need to manually specify the angle brackets
template<typename A, typename B, typename C>
MyTask<A, B, C>* MakeTask(A const& a, B const& b, C const& c) {
    return MyTask<A, B, C>::make(a, b, c);
}
int main() {
    // now usage only needs the function parameters. This scales to variadic args
    auto my_task = MakeTask(1, 2, 3);
    return 0;
}