One nice thing about references is that they can't be null, and you usually don't want to pass a copy of an object.
But in this case, the API would be clearer taking a pointer that defaults to null (using nullptr since we're using C++, not plain C)
void initWith(PinName Bpin, Stream *debugStream = nullptr);
Leave out the second arg and you "get nothing". In the library's implementation, instead of checking a bool flag, you consistently check the pointer itself if (debugStream), and use -> instead of the . operator.
If instead you really want to use a reference, then you could have a special instance. In this case, Stream is an abstract class, but easy enough to add the stubs. First, in my_lib.h
#include <Arduino.h>
extern Stream* const debug_stream_disabled;
#define PinName int // made this up just so it will compile
void initWith(PinName Bpin, Stream &debugStream = *debug_stream_disabled);
That extern says, "someone else has defined a special pointer". It has to be a pointer because Stream is abstract; you can't instances of those. Note also the placement of the const: the variable itself cannot be changed (as opposed to a "non-const pointer to const-Stream")
The resulting function signature is reasonably clear, as when displayed with the IDE's code assistance. Then the implantation in my_lib.cpp
#include "my_lib.h"
namespace {
class : public Stream {
public:
int available() {return 0;}
int read() {return 0;}
int peek() {return 0;}
size_t write(uint8_t) {return 0;}
} doNothingStream;
}
Stream* const debug_stream_disabled = &doNothingStream;
void initWith(PinName Bpin, Stream &debugStream) {
if (&debugStream != debug_stream_disabled) {
debugStream.print("initialized with: ");
debugStream.println(Bpin);
} else {
Serial.println("initialized without debug output"); // just to show the logic works
}
}
The anonymous namespace holds an anonymous concrete subclass with a single instance, which is not directly accessible from the outside. It's used to populate that "magic" pointer, to fulfill the extern
In actual usage, including the init function, you can compare the address of the reference with the magic pointer. This is only strictly necessary with the read functions; otherwise you might wait forever. For writes, you could just call the do-nothing write implementation. (If you're only going to print to it, it would be simpler to use a Print instead of a Stream)
To test this
#include "my_lib.h"
void setup() {
Serial.begin(115200);
initWith(123, Serial);
initWith(456);
initWith(789, Serial);
}
void loop() {}