0

I have a base class from which several classes should inherit. The base class is supposed to be a simple interface, which one inherits for a easy implementation/ for passing data into a library. However, the parameters of the overridden method vary depending on the implementation (inheritance).
In my concrete case, a class, which should parse a file and the number of the used file paths (and their names) varies depending on the implementation.

Example

This is a boiled-down example to the bare minimum because first I'm not allowed to share my code, and secondly to better illustrates my problem.

class Base
{
public:
    Base() = default;
    virtual ~Base() = default;

    // this should be the interface method
    virtual void fn() = 0;
};

class Child1: Base
{
public:
    Child1() = default;
    virtual ~Child1() = default;

    // in this implementation three parameters are needed
    virtual void fn(const std::string& file_path_1, 
                    const std::string& file_path_2, 
                    const std::string& file_path_3) override;
};


class Child2: Base
{
public:
    Child2() = default;
    virtual ~Child2() = default;

    // in this implementation only two parameters are needed
    virtual void fn(const std::string& file_path_1, 
                    const std::string& file_path_2) override;
};

What I have already tried

I already tried to use Variadic arguments, which could theoretically work. Only the readability suffers and it can't be guaranteed at compile time that the required number of arguments was given.

class Base
{
public:
    Base() = default;
    virtual ~Base() = default;

    // this should be the interface method
    virtual void fn(const std::string& file_pats, ...) = 0;
};

I also thought about Variadic Templates, but quickly abandoned this approach due to its complexity/ implementation effort in order to find a more simple solution (but if this is the only viable approach, please tell me).

The Question

Is there a way to have an abstract base class, with a pure virtual method, whose parameters may differ between different implementations?

Moritz
  • 378
  • 5
  • 14
  • 3
    How is `fn` supposed to be called? How does the calling code determine how many arguments they should be providing? – UnholySheep Jun 24 '22 at 21:44
  • 1
    How would this work for the calling code? If you have a `Base*`, how can you know how may parameters to pass to `fn` (and you have to know this at compile time unless you go with the variadic route)? I suspect if you need this pattern you probably always know the exact type of your objects, so you'd probably be better off just defining `fn` in the two subclasses – Dario Petrillo Jun 24 '22 at 21:45
  • The variadic function also doesn't really work. You can not pass-by-reference in a variadic argument and whether passing a `std::string` is supported as variadic argument at all is up to the implementation. – user17732522 Jun 24 '22 at 21:49
  • Exactly this is my problem. I don't know how to design a solution, which accomplishes a inheritance/ interface approach, but with varying parameters. The code provided didn't quiet work, but I'm certain it can be made work (which I don't really want, because it's uggly). I came here in the hope you might know a solution to my dilemma? – Moritz Jun 24 '22 at 21:54
  • 1
    Is this a useful duplicate: [How to achieve variadic virtual member function?](https://stackoverflow.com/q/50316284/10871073) I think the second answer fits (i.e. using an initializer list of `std:string`) would fit your case. – Adrian Mole Jun 24 '22 at 22:04
  • 1
    @Moritz The question is how this interface is supposed to work, even in theory. Take the variadic function version. Can you give an example of how you would use it in your code? How are you deciding on the number of arguments you pass to `fn`? – user17732522 Jun 24 '22 at 22:08
  • Also, you need (at the very least) to correct the glaring errors in your posted code: your child classes aren't (yet) derived from `Base` and they still have the `Base` c'tor and d'tor code in them. ... and there are numerous missing semicolons! – Adrian Mole Jun 24 '22 at 22:11
  • 1
    From what you are showing in the code and the introduction so far, it seems to me that the interface doesn't work even in theory. Instead it seems to me that you would rather want to e.g. pass a single `std::vector` and then fail, e.g. by throwing an exception, if the size doesn't match the expectation of the derived type of the object. – user17732522 Jun 24 '22 at 22:14
  • 2
    @Moritz *"I don't know how to design a solution, which accomplishes a inheritance/ interface approach, but with varying parameters."* -- before you design a solution, you need to define the problem being solved. You have defined your goal in terms of the methods you want to use ("interface", "inheritance", "variadic") instead of the functional requirements. This sets you up to ask how to make a bad approach work instead of asking how to design a good approach; c.f. [XY problem](https://en.wikipedia.org/wiki/XY_problem). *Before designing what will be used, write out **how** it will be used.* – JaMiT Jun 25 '22 at 03:28
  • I edited the question and I hope it is more clear now. I just want to know if and how I can have varying (amount of) parameters from a pure virtual method, or if there is a good pre-existing design/ strategy to tackle this problem. – Moritz Jun 25 '22 at 09:05
  • @JaMiT I do not agree with you that it is the x-y problem. If you have a solution to the problem presented (pure virutal method whose number (not necessarily type) of parameters varies), then please post an answer. What you misinterpret as the x-y problem is (as it should be, according to the Stack Overflow guide for questions) my solution APPROACH, but not a concrete solution. – Moritz Jun 25 '22 at 09:11
  • 1
    @Moritz *"I do not agree with you that it is the x-y problem."* -- you might have misread the abbreviation I used. I used "cf." (confer; compare with), not "i.e." (id est; that is). I think that it is like an XY problem, not that it necessarily is. Similar shortcomings, without necessarily fitting the definition. – JaMiT Jun 25 '22 at 21:54
  • 1
    @Moritz *"If you have a solution [...]"* *"[...] according to the Stack Overflow guide for questions"* -- if I had a solution good enough to post, I would have posted it instead of using a comment to encourage you to improve your question, as per Stack Overflow guidelines. – JaMiT Jun 25 '22 at 21:57
  • @JaMiT Ohh, I'm sorry. I misunderstood that. Thank you for clerifying – Moritz Jun 27 '22 at 14:49

1 Answers1

1

C-style variadic functions should be avoided in C++. Better would be to use variadic template functions, but unfortunately that doesn't really work for virtual functions, see this post for an explanation why.

@AdrianMole and @user17732522 hinted at the right answer: try to find a way to pass a list of things as a single parameter, be that an initializer list or some container like a std::vector (or a view of a container, for example using C++20's std::span).

Finally, if the number of arguments really depends on the derived type, then perhaps fn() should not be a virtual function at all.

G. Sliepen
  • 7,637
  • 1
  • 15
  • 31