0

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?

mikepurvis
  • 1,568
  • 2
  • 19
  • 28
  • Why do you think the compiler should be able to deduce `T`? – Brian Bi May 23 '14 at 18:45
  • 1
    Is MyTask supposed to derive from Task? – Marc Glisse May 23 '14 at 18:48
  • 3
    I think you mean `template Task`, `template static T* make(Args... args)`, and `template class MyTask: public Task> {` blah blah blah, no? – Massa May 23 '14 at 18:48
  • @marc-glisse Yes, you are correct. This is a minimal extracted example, which I have fixed. – mikepurvis May 23 '14 at 19:01
  • @Brian I don't expect it to as written, but I've described the interface I'm wanting, and wondering if someone knows how to template a static method on the type of the class used to identify it. – mikepurvis May 23 '14 at 19:02
  • @mikepurvis: *... on the type of the class used to identify it* cannot be done. That name is not used to identify anything, but to start start lookup. The actual function is always `Task::make` – David Rodríguez - dribeas May 23 '14 at 19:04

3 Answers3

4

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...);
}
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
2

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.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • This seems no better than just `auto my_task = new MyTask(10, 20L, 30.);`. Most of the work is happening in each derived class. – mikepurvis May 23 '14 at 19:14
  • @mikepurvis I thought the whole point was to avoid mentioning those types explicitly. I'm not sure what you mean by the second sentence. If you mean you need to write a wrapper for each derived class, then yes, you do need to do that. But the wrapper is trivial enough that you can do it using a macro if you wish to. – Praetorian May 23 '14 at 19:18
  • @mikepurvis How is this any better (or different) from what I posted 6 minutes beforehand ? – Nikos Athanasiou May 23 '14 at 19:20
  • @mikepurvis Sorry I thought, you had a positive comment here ... my bad – Nikos Athanasiou May 23 '14 at 19:20
  • C++ is a statically typed language. +1 To those who understand this (and know the workarounds) – Nikos Athanasiou May 23 '14 at 19:22
  • That seems like an unnecessarily snarky comment. Everything about this is possible in the context of static types; it comes down to a language design decision that types can be deduced in functions but not constructors. (There's [a C++17 proposal](http://isocpp.org/files/papers/n3602.html) which seeks to change that, fwiw) – mikepurvis May 23 '14 at 19:33
  • @mikepurvis Don't be so quick to make assumptions. fwiw I was the one to upvote your Q so far; I don't find it illogical as a request, but you're downvaluating answers that are perfectly aligned with the current state of the language. Static polymorphism has its uses and limitations. There is dynamic polymorphism as well; If we're talking in terms of standards to come let me update my answer then. – Nikos Athanasiou May 23 '14 at 19:42
1

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;
}
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • In this scenario, the factory is specific to the derived class, so each derived class must have its own `make` method, which kind of loses the point. – mikepurvis May 23 '14 at 19:12