3

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.

fmiz
  • 43
  • 1
  • 5
  • 2
    Just a first symbol in enum names must be a letter, so you could define `stopBits` as `SB1` and `SB2`, or something to that effect. Avoid "analysis paralysis" and use the simplest possible solution, given the knowledge that you currently have. Change the solution once you gain better knowledge about the problem. – Dialecticus Mar 18 '19 at 14:57
  • 1
    If the valid values are contiguous, you can use one of the answers from [this question](https://stackoverflow.com/questions/148511/limiting-range-of-value-types-in-c) to limit the range of a numberal type. – AlbertM Mar 18 '19 at 15:02
  • 2
    btw be careful with your template solution since stopBits<1> and stopBits<2> are different types, so you won't be able to pass it to your serial::serial(...) without serial::serial itself accepting a template parameter. – David Mar 18 '19 at 15:34

2 Answers2

6

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.

rioki
  • 5,988
  • 5
  • 32
  • 55
1

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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • I like this solution... How is the syntactic sugar version made? I'm not doing it right, I'm calling make() from a child class constructor: baudChild(int bps), but bps now is not constexpr. – fmiz Mar 18 '19 at 20:20
  • Well I tried it and it doesn't really work very well. It turns out `condition ? baudRate<256000>() : baudRate<9600>()` is invalid even though both specialisations derive from the same base class. So disregard this bit. – n. m. could be an AI Mar 18 '19 at 21:36