0

I am trying to use std::variant to generalize some of my code. However, I am running issues into when calling a constructor.

I define a class TCPServer as follows:

class TCPServer {
public:
    TCPServer(aio::io_context &io_context, std::variant<PlainServer, PIRServer>);

    std::variant<PlainServer, PIRServer> server_;

private:
    ...
};

And I define classes PIRServer and PlainServer as follows:

class PlainServer : public TCPServer {
public:
    explicit PlainServer(aio::io_context& io_context);
    ...
private:
    ...
};

PlainServer::PlainServer(aio::io_context& io_context) :
    server_config_(server_config),
    TCPServer(io_context, this) {}

I omit PIRServer because it doesn't contribute to the understanding of the problem.

My IDE underlines the initialization of TCPServer in the PlainServer constructor and says: "No matching constructor for initialization of 'TCPServer". Am I using std::variant incorrectly?

peachykeen
  • 4,143
  • 4
  • 30
  • 49
  • Shouldn't it be `*this`? But why would you store a copy of your object in the `variant`? And how does your `TCPServer` contains an object that inherits from it? What's the point of this variant? Why not just use `this` inside `TCPServer`? – KamilCuk Dec 06 '19 at 00:58
  • The reason I am doing this is because I am either going to be making an instance of PIRServer or PlainServer but not both. I thought `std::variant` would give me a nice way to go about specifying only one of the classes to use in `TCPServer`. – peachykeen Dec 06 '19 at 01:02
  • Note: This design requires a base class to know about and count on the existence of a particular derived class. Probably a bad idea. I recommend a rethink. If the coupling is this tight, do you really need the derived class? – user4581301 Dec 06 '19 at 01:10
  • Thank you for your input @user4581301. It has been pretty bad to code this way. I am just trying to get rid of redundancy and make my code generic in case we want to add XServer, YServer, and ZServer into the mix. It doesn't seem like a great idea to merge the TCP code into each of the separate server classes. I would welcome your thoughts though! – peachykeen Dec 06 '19 at 01:19
  • @peachykeen you could be right, but what happens to `server_` when you have a `class ServerWithSprinkles : public TCPServer`? Where does it get the required `PlainServer` from? I don't have a good solution for you, but it's something you need to think about. – user4581301 Dec 06 '19 at 01:26
  • @user4581301 maybe I am misunderstanding but I would just add it to the variant: `std::variant server_;` – peachykeen Dec 06 '19 at 01:29
  • 1
    That leads to a problem with scaling. You keep on having to modify `TCPServer` every time you add a new derived class. Where I think you are headed is better served with a `virtual` function or two. That way the base class or the holder of the base class never needs to know exactly what is on the other end, just that it meets the required interface. Everything is sorted out at runtime with a small performance hit. – user4581301 Dec 06 '19 at 01:45
  • 1
    If you can't do this because the interfaces are too different, you've probably run up against the [Liskov Substitution Principle](https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle) and should reconsider inheritance. – user4581301 Dec 06 '19 at 01:46
  • Thanks for the help @user4581301 I will try drawing out my problem and re-grouping the classes etc. and see where that gets me. In the meantime, the answer below was correct for the given question. – peachykeen Dec 06 '19 at 01:59

1 Answers1

1

Your variant holds a PlainServer. this is a pointer to a PlainServer. You probably want:

class TCPServer {
public:
    TCPServer(aio::io_context &io_context, std::variant<PlainServer*, PIRServer*>);
...
};


PlainServer::PlainServer(aio::io_context& io_context) :
    server_config_(server_config),
    TCPServer(io_context, std::variant<PlainServer*, PIRServer*>(this)) {}

The explicit construction of the variant is required because its single arg constructors are explicit.

Oliver Dain
  • 9,617
  • 3
  • 35
  • 48
  • You are right about the pointers, however, I still have the same error as above. :/ – peachykeen Dec 06 '19 at 01:31
  • Did you explicitly construct a `std::variant` in your initializer list too? – Oliver Dain Dec 06 '19 at 01:32
  • If by that you mean `TCPServer(io_context, std::variant(this))` in my initializer list, then yes. – peachykeen Dec 06 '19 at 01:34
  • Generally you have to initialize the base class first. Could it be that if you re-arrange the initializer list order to put the base class initialization first things work? – Oliver Dain Dec 06 '19 at 01:40
  • sorry, I had a circular dependency that was causing my main error. Your implementation also solved my issue though, so I will mark it as correct. Thanks for the help! – peachykeen Dec 06 '19 at 02:00