C++ has a nice idiom that allows one to write classes that automatically fulfill certain relations between operators. For example this allows to define operator==
and not bother to defined operator!=
as well. This is the idea behind Boost.Operators.
This is an example:
template<class Self> // this class is defined only once
struct equally_comparable{
friend bool operator!=(Self const& s1, Self const& s2){return !(s1==s2);}
};
This class can used repeatedly to enforce consistent logic between ==
and !=
(and avoid errors)
struct A : equally_comparable<A>{ //
int value;
A(int v) : value(v){}
friend bool operator==(A const& a1, A const& a2){return a1.value == a2.value;}
};
int main(){
A a1{4};
A a2{4};
assert(a1 == a2);
A a3{5};
assert(a1 != a3); // uses automatically generated operator !=
}
Now, I want to go one level further and have a class similar to equally_comparable
and define the other function. For example if operator==
is defined then define operator!=
(like above), but also viceversa.
The first naive attempt works
template<class Self>
struct equally_comparable{
friend bool operator!=(Self const& s1, Self const& s2){return !(s1==s2);}
friend bool operator==(Self const& s1, Self const& s2){return !(s1!=s2);}
};
because only one of the two functions need to be defined in struct A
(either operator==
or operator!=
). However it is dangerous because if one forget to define either operator in A
there is an infinite recursion (and a runtime segfault). It also looks fragile.
Is is possible to improve over this and detect that at least one is defined in the derived class at compile time? or more generally is there a generic way to have a class that generates the missing operators? (i.e. a step beyond Boost.Operators).
I would use SFINAE in order to check for comparators at compile time and choose them accordingly.
so this would work as follows:
equal_comparable_tag
enable_if
guard because of function matching the non templated version is preferred. The guard checks if the class is derived from the tagging class.working example
Notes
Be aware, that automatic function signature checking can also be done (usually through type traits and function pointers See this article), but is considerable more complicated. This is required if you want to check if two functions are implemented. Be also aware that you need to check all possible combinations.
i.e.: if you have
operator >
andoperator ==
you'd have to check:and these checks fail if somebody uses implicit conversions e.g.: