Reputation: 11
On my first venture into variadic templates I'm trying to create a function that instantiates a derived class with all combinations of template arguments. The idea being to have a runtime options class be able to select the exact template instantiation to use. I've been able to get the base-case to work with 1 and 2 template arguments. But the general case is not working, the third invocation of SelectInstance below fails to compile with the following error:
candidate function not viable: requires 3 arguments, but 2 were provided AbstractBase *SelectInstance(Func func, Args... args)
The complete code is the following:
#include <memory>
#include <iostream>
struct Options
{
bool GetFirstParameter() { return true; }
bool GetSecondParameter() { return false; }
bool GetThirdParameter() { return true; }
};
struct AbstractBase
{
virtual void PrintMe() = 0;
};
template<class... Args>
struct Derived : public AbstractBase
{
virtual void PrintMe() { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};
template<class... Args>
AbstractBase *SelectInstance()
{
return new Derived<Args...>();
}
template<class Func, class... UnpackedArgs>
AbstractBase *SelectInstance(Func func)
{
if (func())
return SelectInstance<float, UnpackedArgs...>();
else
return SelectInstance<double, UnpackedArgs...>();
}
template<class Func, class... Args, class... UnpackedArgs>
AbstractBase *SelectInstance(Func func, Args... args)
{
if (func())
return SelectInstance<Args..., float, UnpackedArgs...>(args...);
else
return SelectInstance<Args..., double, UnpackedArgs...>(args...);
}
int main()
{
Options opts;
std::unique_ptr<AbstractBase> one(
SelectInstance(
std::bind(&Options::GetFirstParameter, &opts)
)
);
one->PrintMe();
std::unique_ptr<AbstractBase> two(
SelectInstance(
std::bind(&Options::GetFirstParameter, &opts),
std::bind(&Options::GetSecondParameter, &opts)
)
);
two->PrintMe();
// this one fails to compile!
std::unique_ptr<AbstractBase> three(
SelectInstance(
std::bind(&Options::GetFirstParameter, &opts),
std::bind(&Options::GetSecondParameter, &opts),
std::bind(&Options::GetThirdParameter, &opts)
)
);
three->PrintMe();
}
I'm probably misusing the variadic templates improperly to convert functors into a new parameter pack. Any guidance is appreciated.
Upvotes: 1
Views: 94
Reputation: 302718
The reason your attempt fails is that the compiler can't tell which arguments you're providing for what. Let's say our function types are A
, B
, and C
for simplicity. The initial call is:
// Func = A, Args... = {B, C}, UnpackedArgs... = {}
AbstractBase *SelectInstance(Func func, Args... args)
From here, we call SelectInstance<Args..., float, UnpackedArgs...>(args...);
Which is to say, SelectInstance<B, C, float>(b, c);
You're intending that to mean:
// Func = B, Args... = {C}, UnpackedArgs... = {float}
AbstractBase *SelectInstance(Func func, Args... args)
but really parameter packs are greedy and take everything. Args...
takes every subsequent type argument you explicitly provide. So really that call is interpreted as:
// Func = B, Args... = {C, float}, UnpackedArgs... = {}
AbstractBase *SelectInstance(Func func, Args... args)
This function takes three arguments (a B
, a C
, and a float
) but you're only passing two (b
and c
), hence the error.
To fix this, flip the ordering of your template arguments to put the non deduced ones first, and the deduced ones last. This way, you can still use template deduction to do the right thing and even avoid an extra overload:
template<class... Args>
AbstractBase *SelectInstance()
{
return new Derived<Args...>();
}
template<class... UnpackedArgs, class Func, class... Args>
AbstractBase *SelectInstance(Func func, Args... args)
{
if (func()) {
return SelectInstance<float, UnpackedArgs...>(args...);
}
else {
return SelectInstance<double, UnpackedArgs...>(args...);
}
}
This works because now all the arguments you provide go into UnpackedArgs...
and deduction figures out Func
and Args...
- which is exactly what you want.
Upvotes: 1