Reputation: 1628
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?
Upvotes: 0
Views: 869
Reputation: 208323
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...);
}
Upvotes: 4
Reputation: 31499
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;
}
Upvotes: 1
Reputation: 109119
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.);
Another piece of advice is that you change the make()
function to return unique_ptr<...>
instead of a raw pointer.
Upvotes: 2