The goal is to have a helper class to wrap the concept of a resolution safely, encapsulating the range check into the class, since otherwise it can get lost by a client not so cautious.
I think this is all the context that's required, but an additional bit of info that may be helpful is that this looks a bit like what Rust does, where you can build restricted types out of the more common ones. Then again, I don't know Rust, and the above is a bit of a gossip that I've heard, so it may be wrong.
Technically it's just a pair of two unsigned integers of some kind (normal or
long etc.), but the type is similar for both, of course. I want to enforce this
as well, so that no one uses two different types by accident, thus instead of
using std::pair<type, type>
I use Eigen::Matrix<type, 2, 1>
. Eigen is
alright since it's a hard dependency of the project and I'm sure it won't go
away.
Version one (I think it's suboptimal for the reasons given below):
Header:
#ifndef RESOLUTION_H
#define RESOLUTION_H
#include <Eigen/Dense>
class Resolution {
public:
Resolution(unsigned int _x, unsigned int _y);
static Resolution min();
static Resolution max();
inline unsigned int x() const { return res[0]; }
inline unsigned int y() const { return res[1]; }
protected:
Eigen::Matrix<unsigned int, 2, 1> res;
};
#endif
Impl:
#include <cassert>
#include "resolution.h"
Resolution::Resolution(unsigned int _x, unsigned int _y) {
assert(_x < max().x() && _y < max().y());
assert(_x > min().x() && _y > min().y());
res[0] = _x; res[1] = _y;
}
Resolution Resolution::min() { return Resolution{600, 480}; }
Resolution Resolution::max() { return Resolution{3840, 2160}; }
Bad bad because unsigned int
is hardcoded so many times and can get lost if
someone tries to change it later. Attempt 2, fixing it:
Header:
#ifndef RESOLUTION_H
#define RESOLUTION_H
#include <Eigen/Dense>
class Resolution {
public:
typedef unsigned int impl_type;
public:
Resolution(impl_type _x, impl_type _y);
static Resolution min();
static Resolution max();
inline impl_type x() const { return res[0]; }
inline impl_type y() const { return res[1]; }
protected:
Eigen::Matrix<impl_type, 2, 1> res;
};
#endif
Impl:
#include <cassert>
#include "resolution.h"
Resolution::Resolution(impl_type _x, impl_type _y) {
assert(_x < max().x() && _y < max().y());
assert(_x > min().x() && _y > min().y());
res[0] = _x; res[1] = _y;
}
Resolution Resolution::min() { return Resolution{600, 480}; }
Resolution Resolution::max() { return Resolution{3840, 2160}; }
Better but naive because how can I enforce that the max and min are in the range of the type that can be used in the future? And, should I expose the type to the client (i.e. should the typedef be public or protected?)
Attempt 3, that looks like total overkill:
Header:
#ifndef RESOLUTION_H
#define RESOLUTION_H
#include <Eigen/Dense>
template<class T>
class Resolution {
public:
typedef T impl_type;
public:
static Resolution min() { return Resolution{0, 0}; }
static Resolution max() { return Resolution{0, 0}; }
inline impl_type x() const { return 0; }
inline impl_type y() const { return 0; }
protected:
Eigen::Matrix<impl_type, 2, 1> res;
};
template<>
class Resolution<unsigned int> {
public:
typedef unsigned int impl_type;
public:
Resolution(impl_type _x, impl_type _y);
static Resolution min();
static Resolution max();
inline impl_type x() const { return res[0]; }
inline impl_type y() const { return res[1]; }
protected:
Eigen::Matrix<impl_type, 2, 1> res;
};
#endif
Impl:
#include <cassert>
#include "resolution.h"
Resolution<unsigned int>::Resolution(impl_type _x, impl_type _y) {
assert(_x < max().x() && _y < max().y());
assert(_x > min().x() && _y > min().y());
res[0] = _x; res[1] = _y;
}
Resolution Resolution<int>::min() { return Resolution{600, 480}; }
Resolution Resolution<int>::max() { return Resolution{3840, 2160}; }
As said above, total overkill and code duplicaiton, but then it sort of accounts for the different underlying types that can be used.
So, to recap: - Is it worth to wait for a change of the underlying type that can occur?
How good is it to hardcode those constants when the type may change? When it may not change (let's suppose it never will and we assume
unsigned int
)?Is it worth to
typedef
the underlying type in order to avoid duplication and prevent mistakes when it is being changed (again, what if it's never ever changed?)Am I just wsting time? For some reason, I'm afraid to write code not generic at least to a degree because it looks too naive and for more practical reasons as well - I've been taught to anticipate change, though maybe in this case there is no change possible.