Reputation: 43
I'm writing a simple class to setup a serial port on an AVR microcontroller. There are some parameters that have only a few meaningful values, for example baud rate, parity type or the number of stop bits. So, I'd like to create a type, a subset of integers, that can either be 1 or 2. I could make an enum type:
enum stopBits { one, two };
I do not like this solution (spelling out baud rate values?). I have come up with this instead:
template<int vv> struct stopBits {
static_assert( vv == 1 || vv == 2, "stop bit values can be 1 or 2");
int value = vv;
};
// usage:
stopBits<2> s;
I like this a lot more, and I like having a useful error message from compiler output. I would prefer to be able to instead initialize s with a copy constructor:
// I'd like to have this
stopBits s = 2;
This way, I'd be able to write a class with something like:
serial::serial(baudRate b, stopBits s = 1, parity p = none);
Searching for solutions, I found myself going down a rabbit hole: template parameters deduction, bounded::integer library, function parameters that can not be constexpr, this and this. Can this be done or it is best to surrender and move on? Thanks in advance to everyone.
Upvotes: 3
Views: 721
Reputation: 119857
The problem with your solution is that stopBits<1>
and stopBits<2>
are different types, which makes it kinda hard to decide at runtime which one you want to use.
You can do a similar thing with a non-templated class with a private constructor and a friend or static "make" function template that returns an object of the class. Something like this (warning, untested code):
class baudRate {
private:
baudRate(int bps) : bps(bps) {}
public:
template <int bps_> static constexpr baudRate make() {
static_assert(whatever);
return baudRate(bps_);
}
const int bps;
};
auto r = slow ? baudRate::make<9600>() : baudRate::make<115200>();
If you don't like the syntax, it can be made seamless by creating a two-level class hierarchy and moving the static check there, but that would be just syntactic sugar.
Upvotes: 1
Reputation: 6118
You can either have run-time or compile-time checks, but not both.
If you have compile-time checks you are forced to somehow hard code the values. This is the domain of constexpr and static_assert. This is somewhat your first solution.
If you want to pass the value into a constructor you are loosing compile time validation, since you are now assigning values and the compiler does not know what value may be passed to the constructor. You can use things like bounded::integer or roll your own that check the value at run-time and behaves accordingly. (e.g. runtime_error)
The question you need to ask yourself what is an immutable property of the code. If it is immutable (for this use case) you should use template arguments. If it is immutable for this instance, but may vary within a use case, you should use a constant member variable.
Upvotes: 6