sobota, 12 listopada 2016

base class operator== detection

Scenario:
Class for items storage. Items can be added, read, updated. In case of modification callback notification is sent to all listeners.
To discover modification operator==() can be used. It seems to be natural, non-intrusive solution.
Seems to be fine, but problem comes with inheritance.
Consider types:

struct A {
A(int v) : v(v) {}

int v;

friend bool operator==(const A &lhs, const A &rhs)
{
return lhs.v == rhs.v;
}
};
struct B : A {
B(int v, int w) : A(v), w(w) {}

int w;
};


Note that opeartor==() is defined as friend (therefore not class member - see: ADL, friend name injection, Barton–Nackman trick) but it can also be defined as member function or global function.


The potential problem is visible here:

A a1{1}, a2{1};

std::cout << (a1 == a2); B b1{1,1}, b2{1,2}; std::cout << (b1 == b2);


Both print '1', but is 'b1' really equal to 'b2'?
According to current implementation yes, because only A part of B-type object is compared.
The real problem is no notification of such potential problem. And when generic code is used to compare object, where types are defined in different files it might be easy to forget about operator==() definition for class B.
To avoid potential problem it might be better to decide that such situation is prohibited and shall end in compile-time error.
Required is compile time checking if some type (some, because comparison will be used in generic code) has defined operator==.
Tool to solve the problem might be found e.g. in Boost (has_equal_to from typetraits or Concept Check Library), but simple solution is presented here http://stackoverflow.com/a/6536204/122054.
For C++98:

namespace CHECK
{
class No { bool b[2]; };
template No operator== (const T&, const Arg&);

bool Check (...);
No& Check (const No&);

template
struct EqualExists
{
enum { value = (sizeof(Check(*(T*)(0) == *(Arg*)(0))) != sizeof(No)) };
};
}


Simplified version for C++11:

namespace CHECK
{
struct No {};
template No operator== (const T&, const Arg&);

template
struct EqualExists
{
enum { value = !std::is_same<decltype(*(T*)(0) == *(Arg*)(0)), No>::value };
};
}


Using CHECK::EqualExists::value with static assert allows to detect potential problem.

niedziela, 12 czerwca 2016

Old dog,old tricks (in C++)

Compile time safety, compile time error handling - all about typesystem, constness, ...

C-array safety

If function uses constant length array it is tempting to use:


void f(int t[3])
{
...
t[2] = ...;
}

...

int t[3];
f(t);


as array size is merely for human-programmer, it is possible to use:


int t[4];
f(t);


which perhaps is ok (but not nice).
But it is also possible to do


int t[2];
f(t);


Which for sure is wrong.

Solution to ensure strict array size is:


void f(int (&t)[3])
{
...
}


Now only three elements arrays are allowed.
BTW - above construct is frequently used for C-array handling in templates.