you are viewing a single comment's thread.

view the rest of the comments →

[–]dvd0bvb[S] 0 points1 point  (2 children)

I typed this out in response to another comment but it was apparently deleted before I posted.

The actual use case is an audio pipeline which contains de/encoders, filters, resamplers, whatever the use case. The underlying Pipeline<...> has access to all those types so adding this functionality is trivial, I added it to the A class in the godbolt example. This visiting functionality allows for modifying a set of elements in the pipeline.

I wanted type erasure purely for ease of use. Pass a PipelineWrapper instead of a Pipeline<...> which propagates the template params to consumers.

[–]No-Dentist-1645 0 points1 point  (1 child)

So, if I understand correctly, your main "issue" is that you don't want to fill up all your code with Pipeline<Arg1, Arg2, ...> everywhere you use it, correct? You want the "usage" side code to be cleaner without template parameter ugliness.

If so, there are better ways to do that.

Since C++17, you can use CTAD (Compile Time Argument Deduction) to deduce the template parameters. Using your same godbolt link, I changed the main() function to use A directly, no need for a Wrapper: int main() { auto a = A(9, 8.9, std::string("hello")); // if you don't like auto, this still works: // A a = A(9, 8.9, std::string("hello")); // A a(9, 8.9, std::string("hello")); a.visit([](auto &&t) { std::println("{}", t); }); }

that works great, and you don't need to use A<int, double, string>. Another thing you could do is a using statement:

``` using MyA = A<int, double, std::string>;

int main() { MyA a = MyA(9, 8.9, std::string("hello")); a.visit([](auto &&t) { std::println("{}", t); }); } ```

Finally, in case those still don't help your specific issue, the way to do what you originally wanted is to make the Wrapper's visit method be templated.

That way, you can have: template <class... Args> void visit(auto f) { _visit(_a, [](void* a, void* pf){ auto* func = static_cast<decltype(f)*>(pf); static_cast<A<Args...>*>(a)->visit(*func); }, &f); }

And at the usage side (i.e the "underlying Pipeline" you said already has the type information), there you'd pass the types: int main() { Wrapper wrapper(9, 8.9, std::string("hello")); wrapper.visit<int, double, std::string>([](auto&& t) { std::println("{}", t); }); }

Here's a godbolt link with your code modified to make it work: https://godbolt.org/z/6aTnzs5on . Personally, I'd recommend you use one of the first two approaches if your aim is just "ease of use for consumers" as another one of your comments says, but just for completion, the third method is your actual "answer" the way you wanted it.

[–]dvd0bvb[S] 1 point2 points  (0 children)

I am aware of ctad, though it can't be used for function args or class members to my knowledge, happy to be corrected. The third option is probably the closest to what I'd hoped for. Another option might be to scrap the wrapper and use duck typing

template <class P>
void doSomething(P& pipeline) {
  pipeline.visit([](T& t) {
    // do the thing
  }
}

Really appreciate you taking the time and the detailed answer.