The motivation for writing this comes from this interview with Bjarne Stroustrup which, even though unfortunately being fake, makes a good point and I found quite entertaining to read.
Consider this example code in C++ (You can assume vec3
is a class):
int main(int argc, char **argv)
{
vec3 a(1, 2, 3);
const vec3 b(4, 5, 6);
vec3 val1;
auto val2 = a * b;
auto val3 = b * a;
auto val4 = a;
auto val5 = cross_product(a, b);
return 0;
}
Now, try to answer any of these questions:
- What is the content of
val1
? - What is the type of
val2
, and what does the*
operator do? - Is
val3
equal toval2
? - Do mutations of
a
also affectval4
? - Does
cross_product
mutate any ofa
orb
? - What is the type of
val5
?
Ok, so here are the implementations of vec3
and cross_product
:
/*
* Spoiler protection; scroll down for the code
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
class vec3 {
public:
std::vector<double> data;
vec3(double x, double y, double z)
{
this->data(3);
this->data[0] = x;
this->data[1] = y;
this->data[2] = z;
}
/* Create a unit vector in y direction for whatever reason */
vec3():
vec3(0, 1, 0) {};
/** Copy constructor */
vec3(vec3 &original)
{
/*
* I really hope std::vector has a copy constructor, otherwise
* we would create a mystical quantum entanglement between this
* vec3 and `original`! On the other hand, this seemingly
* simple assignment could take an eternity if the vector were
* really long. Either way, implicit copy constructors are broken.
*/
this->data = original.data;
}
/** Get the dot product... */
double operator *(const vec3 &other)
{
return std::sqrt(
this->data[0] * other.data[0]
+ this->data[1] * other.data[1]
+ this->data[2] * other.data[2]
);
}
/**
* ... except if the instance is `const`.
* In that case, multiply value-by-value and return a new `vec3`.
* Just because we can.
*/
vec3 operator *(const vec3 &other) const
{
return vec3(
this->data[0] * other.data[0],
this->data[1] * other.data[1],
this->data[2] * other.data[2]
);
}
}
/* Surprise, cross_product is a class and we called its constructor! */
class cross_product {
public:
vec3 val;
cross_product(vec3 &v1, const vec3 &v2):
val(0, 0, 0)
{
/* randomly mutate something, just because we can */
v1.data[std::rand() % 3] *= (double)std::rand() % 2 + 1;
/* calculate the cross product and store it in this->val */
this->val.data[0] = v1.data[1] * v2.data[2] - v1.data[2] * v2.data[1];
this->val.data[1] = v1.data[2] * v2.data[0] - v1.data[0] * v2.data[2];
this->val.data[2] = v1.data[0] * v2.data[1] - v1.data[1] * v2.data[0];
}
}
I realize this example is really stupid. However, it also is perfectly valid C++; and if it is possible, it has been done before (i.e. some codebase out there looks pretty much like this). Nobody in their right mind would design a language that allows this.
And yet here we are.