0

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.

4 Answers 4

1

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.

Live example.

Sign up to request clarification or add additional context in comments.

7 Comments

But what if I want to call_method to return something?
Declare it accordingly? :) enable_if lets you specify the type, if that's what you mean. Its second template parameter defaults to void .
sizeof...(Us) > 0 will not compile, as > will be interpreted as a template argument list closing bracket. either remove >0 or invert operands
@gmoshkin yes, that's the third option. the original code did not have any parenthesis
@gmoshkin any non-zero value is implicitly convertible to true
|
1

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

1 Comment

auto z = {(Ts::method(), 0) ...}; would work. I'm not sure I get the point of call() helper.
1

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.

1 Comment

Ok this solves the question I asked, but as it turns out I didn't ask the question I needed answered :) I actually want the methods to return a value and I don't want to iterate over them dynamically, seeing as the amount of iterations is known statically. Any help with that?
-1

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.

2 Comments

Since it is effectively my answer, I am curious why you think this is not ideal.
@SergeyA well it looks more like a hack than other solutions and it is kinda harder to read for me at least. I guess it's just personal preference.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.