How to write a library that support both HW and SW Serial communication and allow user to chose which one to use?
The trick here is to go "up a level".  Both HardwareSerial and SoftwareSerial inherit from the Stream class.  It is that class that provides the majority of the interface that you actually use.

So you can provide a constructor with a Stream object alone and use that:

class Foo {
    private:
        Stream *_dev;

    public:
        foo(Stream *dev) : _dev(dev) {}
        foo(Stream &amp;dev) : _dev(&amp;dev) {}

        void begin() {
            _dev-&gt;println("I am ready");
        }
};


Foo myFoo(Serial1);
SoftareSerial btSerial(11,12);
Foo myOtherFoo(btSerial);


What the Stream class doesn't provide, though, is .begin() for setting the baud rate. It has to be left to the user to configure the port before using it.

Serial1.begin(115200);
myFoo.begin(); // --&gt; "I am ready" on Serial 1


or:

btSerial.begin(9600);
myOtherFoo.begin(); // --&gt; "I am ready" on Software serial pins 11/12.


This is known as polymorphism, where an object is seen as one of the parent classes in the inheritance chain and is one of the most powerful aspects of object-oriented programming.

Not having begin in your library is no big deal really. It increases the flexibility (a user can more easily select their baud rate), and it means you don't force them to include SoftwareSerial if they don't need it.

There are other things that inherit Stream, as well, such as a networking Client. So instantly you class can be used with network connections as well as just serial.

So stick to using Stream. It's the accepted way of doing things, and perfectly normal. For example, it's how my ICSC library handles serial objects.  (I used to use the multiple pointer method, but it really really got messy when trying to support other platforms. I ended up with HardwareSerial, SoftwareSerial, USBSerial, Client... a real mess. So much simpler to just use Stream.)



BTW, before you ask, I have two constructors with very slight differences so that you can construct an object either by passing a Stream object by reference (as I do in the examples) or as a pointer (which can be great if you want to use this in another class which itself has a pointer to a Stream object).



Another option is to combine the Stream pointer with a dynamic_cast to try and identify the type of object being passed.

void Foo::begin(int baud) {
    if (HardwareSerial *hs = dynamic_cast&lt;HardwareSerial *&gt;(_dev)) {
        hs-&gt;begin(baud);
    } else if (SoftwareSerial *ss = dynamic_cast&lt;SoftwareSerial *&gt;(_dev)) {
        ss-&gt;begin(baud);
    } else { 
        // We don't know what to do here
        // so do nothing?
    }
}


In this way dynamic_cast is being used like Java's instanceof operator to work out what kind of object it is that is being used.

Of course, that then means that you're really restricting the library to only working with those two types of object, but it is still possible to pass other class types as long as the user pre-configures them before calling your begin function. Which gets confusing - which type does the user need to .begin() and which types have .begin() called for you? It's just cleaner to always have it as the user must call .begin() themselves - there's no ambiguity then.