简体   繁体   中英

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

Minimal example:

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)
  {
  }
}

The make factory method exists to get me out of having to supply all the template types when the templated, derived classes are instantiated.

I would like to be able to create an instance of MyTask as succinctly as possible, ie:

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

However, the compiler is insisting that it can't deduce T, and wants instead:

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

It's not a huge deal-breaker, but the repetition seems unnecessary. Is there some way to get it the way I want?

The problem is that MyTask::make is not really that, but rather Task::make . The name that you use in the call is the start point for lookup, but not the function name. So technically there is no repetition.

There are different things that you could do, like for example, use the CRTP (as Massa suggests in a comment) to have the derived type inject it's type into the base --at the cost of having different types for different bases, if you have some actual interface you will need to provide the CRTP as an intermediate helper type between Task and MyTask , or better as an external 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);

Another alternative is to make the function a free function and pass only the type that you want to build:

template <typename T, typename ...Args>
T* make(Args ... args);

auto x = make<MyTask>(a,b,c); // only named once here

To support the nice syntax in the above code for a template without having to provide the template arguments, you can implement make in terms of a template rather than a type :

template <template <typename...> class T,
          typename ... Args>
T<Args...>* make(Args... args) {
    return new T<Args...>(args...);
}

Your question doesn't make sense for a couple of different reasons - make() is a member of Task , but you talk about MyTask::make() ; did you intend for MyTask to derive from Task ? Also, here

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

Obviously you cannot use MyTask without specifying template arguments.

I think what you were trying to demonstrate was this (I've added perfect forwarding):

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) {}
};

And you'd use it as

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

which is quite verbose. This can be avoided by creating a wrapper function that delegates to Task::make() (or get rid of the middleman and do the work done in Task::make within this wrapper itself, if that's feasible)

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));
}

Intended usage:

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

Live demo

Another piece of advice is that you change the make() function to return unique_ptr<...> instead of a raw pointer.

A helper function would be of value; Then template argument deduction could happen through it

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM