Strictly speaking types are compile-time constructs in C++. However there are times where some constant runtime characteristics of an object are defined effectively a runtime type.
For example, if one has a geometrical vector, where, due to framework restrictions the dimension is only known at runtime (otherwise I would make the dimension a template parameter), for example read from a file.
The fact that the dimension is runtime, doesn't mean that one should be able to compare, assign or combine vectors with different dimension. If one wants to introduce this logic into the code, one will have to restrict a bunch of operations. This can be done by asserts or by throwing, only at runtime.
So, for example, one would implement this,
class vec{
std::vector<double> impl_;
public:
vec(std::size_t size) : impl_(size){}
bool operator==(vec const& other) const{
if(impl_.size() != other.impl_.size()) throw std::domain_error("cannot compare vectors of different size");
return std::equal(impl_.begin(), impl_.end(), other.impl_.begin());
}
bool operator<(vec const& other) const{
if(impl_.size() != other.impl_.size()) throw std::domain_error("cannot compare vectors of different size");
return impl_ < other.impl_;
}
vector& operator=(vector const& other){
if(impl_.size() != other.impl_.size()) throw std::domain_error("");
std::copy(other.impl_.begin(), other.impl_.end(), impl_.begin());
return *this;
}
};
vec is implemented in terms of std::vector and will never change size after construction.
The problem with this is that one ends up writing the restriction in all functions.
My question is if there is any idioms or facilities that can help with this, by restricting (at runtime) operations that are performed with incompatible objects, and in particular helping writing less repeated code.
int v1_dim = 4;
int v2_dim = 3;
vec v1(v1_dim); // v1 has (forever) dimension 4, 4 is essential to v1
vec v2(v2_dim); // v2 has (forever) dimension 3
v1 = v2; // this is a logic error, I can't prevent the compilation of this unfortunately; So I will at least do a runtime check
v1 == v2; // this is a logic error, runtime error
Note that I can't use std::array<double, 4> or 3> because 4 and 3 could be runtime variables.
Does this concept of effective runtime types has a name? Has it been investigated?
Other examples where this can happen:
1) Imagine having variable sized NxN arrays and trying to define mathematical addition. N will be runtime (and even constant after construction) and one will have to check that one is not adding arrays of different sizes, also one would like any incompatible comparison to be trigger an error.
2) More generally, this philosophical problem can appear in a class with constant members. This is kind of contrived, but suppose one is forced to have const members
struct employee{
std::string const Name; // Once the employee is created it won't change name
double salary;
employee& operator=(employee const& other);
};
one could restrict this:
employee& employee::operator=(employee const& other){
if(Name != other.Name) throw std::domain_error("cannot change name");
salary = other.salary;
return *this;
}
The only automatic solution that I imagine is to systematically overload some operator (such as operator=() const) in member types to force this.
struct employee{
My_string const Name; // Once the employee is created it won't change name
double salary;
employee& operator=(employee const& other){
Name = other.Name; //simply
salary = other.salary;
}
};
but where one depends (relies) on special member types with, for example:
My_string const& My_string::operator=(My_string const& other) const{
if(*this != other) // this is the "real" inequality (not the restricted one), probably it will be if(this->std::string::operator=(other)) if My_string is derived from std::string
throw std::domain_error("assignment to const doesn't preserve value");
return *this;
}
and later to be consistent and systematic one would write:
employee const& employee::operator=(employee const& other) const{
if(*this != other) throw std::domain_error("assignment to const doesn't preserve value");
return *this;
}