Reputation: 1135
So basically I want to define a class that inherits from an arbitrary amount of classes and have a method in it that calls the overloaded method from all the base classes.
I tried writing this, but it won't compile:
class Foo
{
public:
void method()
{
std::cout << "Foo::method()\n";
}
};
class Bar
{
public:
void method()
{
std::cout << "Bar::method()\n";
}
};
template <typename... Ts>
class Combined: public Ts...
{
public:
Combined(const Ts&... ts): Ts(ts)... {}
Combined(Ts&&... ts): Ts(std::move(ts))... {}
template <typename U>
void call_methods()
{
U::method();
}
template <typename U, typename... Us>
void call_methods()
{
U::method();
call_methods<Us...>();
}
void method()
{
call_methods<Ts...>();
}
};
int main(int argc, char *argv[])
{
Combined<Foo, Bar> obj({}, {});
obj.method();
return 0;
}
The compiler says the following:
test.cpp:42:9: error: call to member function 'call_methods' is ambiguous
call_methods<Us...>();
^~~~~~~~~~~~~~~~~~~
test.cpp:47:9: note: in instantiation of function template specialization
'Combined<Foo, Bar>::call_methods<Foo, Bar>' requested here
call_methods<Ts...>();
^
test.cpp:57:9: note: in instantiation of member function
'Combined<Foo, Bar>::method' requested here
obj.method();
^
test.cpp:33:10: note: candidate function [with U = Bar]
void call_methods()
^
test.cpp:39:10: note: candidate function [with U = Bar, Us = <>]
void call_methods()
^
Basically there is an ambiguity between call_methods<U = Bar>
and call_methods<U = Bar, Us = <>>
. But if I declare a void call_methods() {}
, it won't match the call_methods<Us...>();
for some reason.
If it's not clear yet, I want Combined<Foo, Bar>::method()
to call Foo::method()
and Bar::method()
.
I know that I can probably implement this by having a tuple
with the objects of corresponding types as a member and just iterating over them, but I really want to find a solution that's closer to what I wrote.
Upvotes: 0
Views: 780
Reputation: 55425
To fix your solution, disable the second overload when parameter pack is empty:
template <typename U, typename... Us>
typename std::enable_if< (sizeof...(Us) > 0) >::type
call_methods()
{
U::method();
call_methods<Us...>();
}
Gets rid of ambiguity.
Upvotes: 1
Reputation: 11250
Tackling the why of the error, this happens because overload resolution is concerned with function parameters and not function template parameters.
The instantiation foo<Bar>()
is indistinguishable from overload resolution (the one with one parameter or the one with an empty parameter pack?), hence it ends up with an ambiguity in the call.
As mentioned in the SergeyA's answer, a way to solve this is to have just one overload and do in-site
expansion of the call.
Upvotes: 1
Reputation: 62613
There are multiple solutions to this problem. The easiest would be to expand call in place, rather than recursively. Something along following lines:
struct Foo
{
void method();
};
struct Bar
{
void method();
};
template <typename... Ts>
class Combined: public Ts...
{
public:
Combined(const Ts&... ts);
Combined(Ts&&... ts);
void method()
{
bool z[] = { (Ts::method(), true)... };
(void)z;
}
};
int main(int argc, char *argv[])
{
Combined<Foo, Bar> obj({}, {});
obj.method();
return 0;
}
Upvotes: 1
Reputation: 1135
One of the solutions:
template <typename... Ts>
class Combined: public Ts...
{
public:
Combined(const Ts&... ts): Ts(ts)... {}
Combined(Ts&&... ts): Ts(std::move(ts))... {}
void method()
{
int dummy[] { (Ts::method(), 0)... };
}
};
Not ideal but should be as efficient as my original idea.
Upvotes: -1