我正在编写一个简单的类来在AVR微控制器上设置串行端口。 有些参数只有几个有意义的值,例如波特率,奇偶校验类型或停止位数。因此,我想创建一个类型,整数的子集,可以是1或2。我可以创建一个枚举类型:
enum stopBits { one, two };
我不喜欢这种解决方案(拼出波特率值吗?)。 我想出了这个:
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;
我非常喜欢这个,而且我喜欢从编译器输出中获得有用的错误消息。我希望能够使用复制构造函数来初始化s:
// I'd like to have this
stopBits s = 2;
这样,我将能够编写一个类似以下内容的类:
serial::serial(baudRate b, stopBits s = 1, parity p = none);
在寻找解决方案时,我发现自己陷入了困境:模板参数推导,bounded :: integer库,不能为constexpr的函数参数,this和this。 可以做到这一点,还是最好投降并继续前进?预先感谢大家。
答案 0 :(得分:6)
您可以进行运行时检查或编译时检查,但不能同时进行。
如果进行编译时检查,则必须以某种方式对值进行硬编码。这是constexpr和static_assert的域。这是您的第一个解决方案。
如果要将值传递给构造函数,则将失去编译时验证,因为您现在正在分配值,并且编译器不知道可能将什么值传递给构造函数。您可以使用诸如bounded :: integer之类的内容,也可以滚动自己的内容以在运行时检查该值并相应地表现。 (例如runtime_error)
您需要问自己的问题是什么是代码的不可变属性。如果它是不可变的(对于此用例),则应使用模板参数。如果对于该实例它是不可变的,但是在用例中可能有所不同,则应使用常量成员变量。
答案 1 :(得分:1)
解决方案的问题在于stopBits<1>
和stopBits<2>
是不同的类型,这使得在运行时很难确定要使用哪个类型。
您可以对具有私有构造函数的非模板类以及返回该类对象的好友或静态“ make”函数模板进行类似操作。这样的东西(警告,未经测试的代码):
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>();
如果您不喜欢该语法,则可以通过创建两级类层次结构并将静态检查移到那里来使其无缝化,但这只是语法糖。