Why does my templated operator== not get used? - c++

I have defined operator== as follows:
template <class State>
bool operator==(const std::shared_ptr<const State> &lhs,
const std::shared_ptr<const State> &rhs) {
return *lhs == *rhs;
}
This operator does not get instantiated (in gdb, I cannot set the break-point on the return statement -- the line does not exist).
However, this operator should be used by std::find called in this line:
return std::find(v.begin(), v.end(), el) != v.end();
I checked the type of v in the above line in gdb:
(gdb) whatis v
type = const std::vector<std::shared_ptr<Domains::IncWorst const>> &
(gdb) whatis el
type = const std::shared_ptr<Domains::IncWorst const> &
Doesn't this match my templated operator== with State being IncWorst?
I implemented a toy example as follows and the example works, so I cannot understand why the real code does not.
template<class V, typename T>
bool in(const V &v, const T &el) {
return std::find(v.begin(), v.end(), el) != v.end();
}
struct MyState {
MyState(int xx) : x(xx) {}
bool operator==(const MyState &rhs) const {
return x == rhs.x;
}
int x;
};
template <class State>
bool operator==(const std::shared_ptr<const State> &lhs,
const std::shared_ptr<const State> &rhs) {
return *lhs == *rhs;
}
int main() {
std::vector<std::shared_ptr<const MyState>> v{
std::make_shared<const MyState>(5)};
auto p = std::make_shared<const MyState>(5);
std::cout << in(v, p) << std::endl; // outputs 1
return 0;
}

Your operator== template is in the wrong namespace.
In order to be found by ADL, it must be either in the std namespace (which would be illegal, per [namespace.std]/1) or in Domains (per [basic.lookup.argdep]/2).
However, this is still highly dangerous, since if any template performing an equality comparison (e.g. but not limited to std::find) is instantiated both before and after your operator== template is declared, your whole program will be invalid per [temp.point]/8 and [basic.def.odr]/6.
If you must provide operator overload templates for std::shared_ptrs of your types, prefer to explicitly instantiate them after the declaration of each class, such that there is less chance of a template being instantiated somewhere the class is not visible:
struct MyState {
// ...
};
template bool operator==<MyState>(
const std::shared_ptr<MyState const>&,
const std::shared_ptr<MyState const>&);
This could still be problematic if someone forward-declares MyState somewhere else, but it's probably the best you can do.

Related

How can I write a custom STL iterator for a class type without using raw pointers? Is there a practical advantage to this?

The title of this question used to be: Are there practical advantages to creating an iterator class compared to returning raw pointers from begin and end functions?
Recently I have been working on a code base which uses MFC and objects such as CArray<T, U>.
Some parts of new code which has been written make use of the STL and <algorithm> library.
For example
CArray<int int> carray;
carray // do stuff
std::vector<int> stlvector(begin(carray), end(carray));
stlvector.dostuff() // do stuff
I recently asked a question about creating iterators for a class such as CArray, which I do not have access to.
I now have some further questions about this. The first question can be found here. Here is my second question:
Should the begin and end functions return raw pointers or iterators?
In the linked question above, an example was provided as an answer which returns raw pointers. This answer was very similar to the implementation I used.
template<typename T, typename U>
auto begin(const CArray<T, U> &array>)
{
return &array[0];
}
template<typename T, typename U>
auto end(const CArray<T, U> &array>)
{
return (&array[array.GetCount() - 1]) + 1;
}
These functions return raw pointers. However I attempted to implement an iterator solution. So far I have not been successful.
The main reference which I used during my research can be found here:
https://internalpointers.com/post/writing-custom-iterators-modern-cpp
First attempt
This is the first attempt that I made in finding a solution.
You can play with this code here.
#include <iostream>
#include <iterator>
#include <algorithm>
template <typename U>
class CArrayForwardIt
{
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = U;
using pointer = U*;
using reference = U&;
public:
CArrayForwardIt(pointer ptr)
: m_ptr(ptr)
{
}
// = default?
//CArrayForwardIt(CArrayForwardIt<U> other)
// : m_ptr(ptr)
// {
// }
reference operator*() const
{
return *m_ptr;
}
// what does this do, don't understand why operator-> is needed
// or why it returns a U* type
pointer operator->()
{
return m_ptr;
}
CArrayForwardIt& operator++()
{
++ m_ptr;
return *this;
}
CArrayForwardIt operator++(int)
{
CArrayForwardIt tmp(*this);
++ (*this);
return tmp;
}
friend bool operator==(const CArrayForwardIt& lhs, const CArrayForwardIt& rhs)
{
return lhs.m_ptr == rhs.m_ptr;
}
friend bool operator!=(const CArrayForwardIt& lhs, const CArrayForwardIt& rhs)
{
return !(lhs == rhs);
}
private:
pointer m_ptr;
};
template<typename T, typename U>
auto begin(const CArray<T, U> &array)
{
return CArrayForwardIt<U>(&array[0]);
}
template<typename T, typename U>
auto end(const CArray<T, U> &array)
{
return CArrayForwardIt<U>((&array[array.GetCount() - 1]) + 1);
}
int main()
{
CArray<int, int> c;
// do something to c
std::vector<int> v(begin(c), end(c));
return 0;
}
This is what happens when I try to compile this (with Visual Studio 2019 Pro).
no instance of constructor "std::vector<_Ty, _Alloc>::vector [with _Ty=int, _Alloc=std::allocator<int>]" matches argument list
'<function-style-cast>': cannot convert from 'contt TYPE*' to 'std::CArrayForwardIt<U>'
'std::vector<int, std::allocator<int>>::vector(std::vector<int, std::allocator<int>> &&, const _Alloc &) noexcept(<expr>)': cannot convert from argument 1 from 'void' to 'const unsigned int'
Being more familiar with gcc, I have little knowledge of how to understand this.
Second attempt
I made another two further attempts at this but they were quite similar.
One was to change my class CArrayForwardIt to inherit from iterator<std::forward_iterator_tag, std::ptrdiff_t, U, U*, U&>, and to remove the using... lines at the top of the class. This didn't seem to get me any closer to a solution.
In addition, I looked at the constructor definition for std::vector. See here.
I may be misunderstanding here, but it looks like std::vector requires a InputIt type argument.
Therefore I tried to change my class to be something like this:
#include <iostream>
#include <iterator>
#include <algorithm>
template <typename U>
class forward_iterator
{
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = U;
using pointer = U*;
using reference = U&;
public:
forward_iterator(pointer ptr)
: m_ptr(ptr)
{
}
// = default?
//forward_iterator(forward_iterator<U> other)
// : m_ptr(ptr)
// {
// }
reference operator*() const
{
return *m_ptr;
}
// what does this do, don't understand why operator-> is needed
// or why it returns a U* type
pointer operator->()
{
return m_ptr;
}
forward_iterator& operator++()
{
++ m_ptr;
return *this;
}
forward_iterator operator++(int)
{
forward_iterator tmp(*this);
++ (*this);
return tmp;
}
friend bool operator==(const forward_iterator& lhs, const forward_iterator& rhs)
{
return lhs.m_ptr == rhs.m_ptr;
}
friend bool operator!=(const forward_iterator& lhs, const forward_iterator& rhs)
{
return !(lhs == rhs);
}
private:
pointer m_ptr;
};
template<typename T, typename U>
auto begin(const CArray<T, U> &array)
{
return forward_iterator<U>(&array[0]);
}
template<typename T, typename U>
auto end(const CArray<T, U> &array)
{
return forward_iterator<U>((&array[array.GetCount() - 1]) + 1);
}
int main()
{
CArray<int, int> c;
// do something to c
std::vector<int> v(begin(c), end(c));
return 0;
}
This, perhaps unsurprisingly, did not compile either. At this point I became confused. std::vector appears to demand an InputIt type, which forward_iterator should work for, but it doesn't seem to make sense to redefine what forward_iterator is, even if I write this class outside of namespace std.
Question
I am fairly sure there should be a way to write an iterator class for the MFC CArray, which can be returned by begin and end functions. However, I am confused as to how to do this.
Further to the question of writing a working solution, I am beginning to wonder if there are any practical advantages to doing this? Does what I am trying to do even make sense? The raw pointer solution clearly works, so are there any advantages of investing the effort to write an iterator based solution? Can iterator solutions provide more sophisticated bounds checking, for example?
Since I managed to get this working I wanted to post a solution, hopefully I don't make too many errors transcribing it.
One thing that was not shown in the above code snippet is the fact that all these class and function definitions existed inside of namespace std. I posted another question about this earlier, and was informed that these things should not be inside namespace std. Correcting this seems to have resolved some problems and made the solution a step closer.
You can find that question here.
This is what I have so far: This is how to write an iterator for an external class which the programmer does not have access to. It also works for your own custom types or containers which you do have access to.
// How to write an STL iterator in C++
// this example is specific to the MFC CArray<T, U> type, but
// it can be modified to work for any type, note that the
// templates will need to be changed for other containers
#include <iterator>
#include <mfc stuff...>
template<typename T, typename U>
class CArrayForwardIt : public std::iterator<std::forward_iterator_tag, std::ptrdiff_t, U, U*, U&>
{
// the names used in this class are described in this list
// using iterator_category = std::forward_iterator_tag;
// using difference_type = std::ptrdiff_t;
// using value_type = U;
// using pointer = U*;
// using reference = U&;
public:
CArrayForwardIt(CArray<T, U> &array_ref, const std::size_t index)
: m_array_ref(array_ref)
, m_index(index)
{
}
// the only way I could get this to work was to make the return type
// an explicit U&, I don't know why this is required, as using
// reference operator*() const did not seem to work
U& operator*() const
{
if(m_index < m_array_ref.GetCount())
{
return m_array_ref[m_index];
}
else
{
throw std::out_of_range("Out of range Exception!");
}
}
CArrayForwardIt& operator++()
{
++ m_index;
return *this;
}
CArrayForwardIt operator++(int)
{
CForwardArrayIt tmp(*this);
++(*this);
}
friend bool operator==(const CArrayForwardIt& lhs, const CArrayForwardIt& rhs)
{
if(&(lhs.m_array_ref) == &(rhs.m_array_ref))
{
return lhs.m_index == rhs.m_index;
}
return false;
}
friend bool operator!=(const CArrayForwardIt& lhs, const CArrayForwardIt& rhs)
{
return !(lhs == rhs);
}
private:
std::size_t m_index;
CArray<T, U> &m_array_ref;
};
template<typename T, typename U>
auto begin(CArray<T, U> &array)
{
return CArrayForwardIt<T, U>(array, 0);
}
template<typename T, typename U>
auto end(CArray<T, U> &array)
{
return CArrayForwardIt<T, U>(array, array.GetCount());
}
int main()
{
CArray<int, int> array;
// do stuff to array
// construct vector from elements of array in one line
std::vector<int> vector(begin(array), end(array));
// also works with other STL algorithms
}
Note my comment about the U& operator* which produced some compiler error when written as reference operator* which might be a Visual Studio compiler bug. I'm not sure about this.
I would suggest that although this method is more difficult to implement (but not much when you know how to do it) it has the advantage of not using raw pointers which means that the iterator functions can provide proper exception throwing statements when illegal operations are attempted. For example, incrementing the iterator when it is already at the end.
Useful references:
https://lorenzotoso.wordpress.com/2016/01/13/defining-a-custom-iterator-in-c/
https://internalpointers.com/post/writing-custom-iterators-modern-cpp
For completeness, here is the simpler solution using raw pointers.
template<typename T, typename U>
auto begin(CArray<T, U> &array)
{
return &(array[0]);
}
template<typename T, typename U>
auto end(CArray<T, U> &array)
{
// get address of last element then increment
// pointer by 1 such that it points to a memory
// address beyond the last element. only works for
// storage containers where higher index elements
// are guaranteed to be at higher value memory
// addresses
if(array.GetCount() > 0)
{
return &(array[array.GetCount() - 1]) + 1;
}
else
{
return &(array[0]) + 1;
}
}
You can use these in the same way as demonstrated in the other answer, however there is also a way to use STL vector without the begin and end functions:
CArray<int, int> array; // ... do something to array
std::vector<int> vec(&array[0], &(array[array.GetCount() - 1]) + 1);
// note only works if elements guaranteed to be in continuous
// packed memory locations
but it also works with begin and end which is nicer
std::vector<int> vec(begin(array), end(array));

How to avoid specializing iterator_traits for each possible instantiation of a templated iterator?

I want to pimp up my ranged based for loops, for example by enabling reversed iteraton. I managed to get it working to some degree by writing an adaptor, but I am lost on how to make the adpaptor composable.
#include <iostream>
template <typename IT> struct reversed_range {
struct reversed_iterator {
IT it;
reversed_iterator(IT it): it(it){}
reversed_iterator& operator++(){
--it;
return (*this);
}
typename std::iterator_traits<IT>::reference operator*(){
IT tmp = it;
--tmp;
return *tmp;
}
bool operator==(const reversed_iterator& other) const {
return it == other.it;
}
bool operator!=(const reversed_iterator& other) const {
return !(*this == other);
}
};
IT itbegin;
IT itend;
reversed_range(const IT& b,const IT& e): itbegin(b),itend(e){}
reversed_iterator begin() const { return reversed_iterator(itend); }
reversed_iterator end() const { return reversed_iterator(itbegin); }
};
template <typename IT>
reversed_range<IT> reverse_range(const IT& begin,const IT& end) {
return reversed_range<IT>(begin,end);
}
template <typename C>
reversed_range<C> reverse_range(const C& c) {
return reversed_range<typename C::iterator>(std::begin(c),std::end(c));
}
int main() {
int x[] = {1,2,3,4,5};
for (auto y : reverse_range(std::begin(x),std::end(x))){
std::cout << y << "\t";
}
for (auto y : reverse_range(reverse_range(std::begin(x),std::end(x)))){
std::cout << y << "\t";
}
return 0;
}
The first loop works like a charm, but for the second I get the error:
error: no type named ‘reference’ in ‘struct std::iterator_traits<reversed_range<int*> >’
typename std::iterator_traits<IT>::reference operator*(){
^~~~~~~~
I know why this happens and I know how I could fix it for this particular case. However, if i correclty understand this answer I would have to specialize iterator_traits for each possible instantiation of my reversed_iterator which isnt really feasible. I am a bit lost, most of the time I write my own iterators I find myself writing lots of boilerplate just to reach a point where I realize that I would need exponentially more boilerplate to get it working. I mean I didnt even start to consider const_iterators.
Is there a way to get the above working (whithout having to specialize iterator_traits for each iterator I ever want to reverse?
PS: tagged as C++11, because thats my current scope, but if there are improvements with respect to this, I wouldnt mind to use a newer standard.
std::iterator_traits<Iterator>::something is simply Iterator::something by default. Thus, simply add the typedefs into your reversed_iterator type:
struct reversed_iterator {
using difference_type = typename std::iterator_traits<IT>::difference_type;
using value_type = typename std::iterator_traits<IT>::value_type;
using pointer = typename std::iterator_traits<IT>::pointer;
using reference = typename std::iterator_traits<IT>::reference;
using iterator_category = /* appropriate category */;
// ...
};

Problems overloading operator== of template sub class

I've problems overloading operator==, different compiler errors using VC++(2015) and g++ 5.4.0 (--std==c++14). Here's the code (this is just an extract of a more complex situation in my real code base):
#include <vector>
template<typename T>
struct A {
struct B {
std::vector<T> _elements;
// Internal cmp op.
bool operator==(const B &other) {
return _elements == other._elements;
}
};
std::vector<B> _entries;
};
// External cmp op.
template<typename T>
inline bool operator==(typename const A<T>::B &l, typename const A<T>::B & r) {
return l._elements == r._elements;
}
int main() {
A<int>::B b0, b1;
b0.operator==(b1); // a
operator==<int>(b0, b1); // b
b0 == b1; // c
std::vector<A<int>::B> v0, v1;
std::equal(v0.begin(), v0.end(), v1.begin()); // d
v0 == v1; // e
return 0;
}
I do not add the error messages, because I have the german version of VC++ and the g++ errors span over many lines.
VC++ gives an error on (e). I don't understand why, because vector<>::operator== seems to call std::equal internally and (d) compiles fine. Why does this fail?
g++ fails to accept my external operator==(), so totally fails to compile this short code. I have no idea how to write an external operator==() for A<T>::B that works with both compilers.
I haven't tried clang yet.
Many thanks.
There were two errors in your program:
// Internal cmp op.
bool operator==(const B &other) const {
///// <- here
return _elements == other._elements;
}
should be a const member and the keyword const cannot appear just behind the typename:
// External cmp op.
template<typename T>
inline bool operator==(typename A<T>::B const& lhs, typename A<T>::B const& rhs)
///// <- here -> ////
{
return lhs._elements == rhs._elements;
}
Live Example
Note that the placement of const is usually fairly liberal in C++, e.g. you can write both const typename A<T>::B & lhs and typename A<T>::B const& lhs, but the form you chose, typename const A<T>::B & lhs is not allowed.
Also note that you want to write either a member operator== or a non-member operator==, but never both. In your case, because T is not deducible in typename A<T>::B, you have to write the ugly operator==<int>(b0, b1) to select the non-member.
I would remove the non-member template operator== and add to your class template A<T> a non-member
bool operator==(const A &other) const {
return _entries == other._entries;
}
so that you can also compare objects of A<int>. Note that this will call the standard library operator== for std::vector which in turn will call your operator== for B.

How to determine if a class implements != operator overloading?

Given the following function:
template <typename T>
static bool equals(const std::vector<T> &a, const std::vector<T> &b) {
if (a.size() != b.size())
return false;
for (size_t i = 0; i < a.size(); ++i)
if (a[i] != b[i]) // Requires that the != operator is supported by the template type.
return false;
return true;
}
how can I determine if the given T overrides the != operator? If someone uses a type without overloading it might end up using a simple binary comparison which might silently lead to wrong results. So I want to ensure only classes that have their own != operator overloading can be used here.
[update 1 - boost 'semi' solution]
I realized that example from my first answer does not work if your class has conversion operator (to type which allows for != comparision), to fix it you can use boost has_not_equal_to type trait. Still its not perfect, as in some cases it generates compilation error instead of giving a value. Those cases are listed in provided link.
[update 2 - concepts solution]
Example with use of concepts:
#include <iostream>
#include <vector>
template<typename T>
concept bool OperatorNotEqual_comparable()
{
return requires (T a, T b) {
{ a.operator!=(b) } -> bool;
};
}
template <OperatorNotEqual_comparable T>
static bool equals(const std::vector<T> &a, const std::vector<T> &b) {
if (a.size() != b.size())
return false;
for (size_t i = 0; i < a.size(); ++i)
if (a[i] != b[i]) // Requires that the != operator is supported by the template type.
return false;
return true;
}
struct sample1{
bool operator!=(const sample1&) const { return true; }
};
struct sample2{
};
struct sample3{
operator void*() { return 0; }
};
int main() {
// Compiles ok!
std::vector<sample1> vec1;
equals(vec1, vec1);
// Fails, which is OK!
//std::vector<sample2> vec2;
//equals(vec2, vec2);
// Fails, which is OK!
//std::vector<sample2*> vec2;
//equals(vec2, vec2);
// Fails, which is OK!
//std::vector<int> vec4;
//equals(vec4, vec4);
// Fails, which is OK!
//std::vector<sample3> vec5;
//equals(vec5, vec5);
}
http://melpon.org/wandbox/permlink/txliKPeMcStc6FhK
[old answer - SFINAE solution, does not check for conversion operator]
You can use SFINAE, and in the near future concepts (they are in gcc 6.0),
#include<iostream>
#include<string>
#include<type_traits>
template<typename T>
class has_not_equal{
template<typename U>
struct null_value{
static U& value;
};
template<typename U>
static std::true_type test(U*,decltype(null_value<U>::value!=null_value<U>::value)* = 0);
static std::false_type test(void*);
public:
typedef decltype(test(static_cast<T*>(0))) type;
static const bool value = type::value;
};
struct sample1{
bool operator!=(const sample1&) { return true; }
};
struct sample2{
};
int main(){
std::cout<<std::boolalpha;
std::cout<<has_not_equal<int>::value<<std::endl;
std::cout<<has_not_equal<std::string>::value<<std::endl;
std::cout<<has_not_equal<sample1>::value<<std::endl;
std::cout<<has_not_equal<sample2>::value<<std::endl;
}
output:
g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
true
true
true
false
live
above code is a modified version from this site, it was for operator==, I changed it to operator!=
If T has no operator !=, then the template wont get instantiated for that type, but you will get a (possibly horribly long and unreadable) error message from your compiler. On the other hand, it T has a operator !=, then it should be just fine to use it. There wont be silent wrong result, unless Ts operator != is anyhow broken.
The only other (besides the conversion) case (I can think of) where a binary comparison can happen (has operator!= defined) and lead to silently wrong results is when T is actually a pointer and you expect a "deep comparison".
One could add an overload for vectors containg pointers but that wouldn't cover pointer to array storage.
template <typename T>
static bool equals(const std::vector<T> &a, const std::vector<T> &b)
{
return std::equal(a.begin(), a.end(), b.begin());
}
template <typename T>
static bool equals(const std::vector<T *> &a, const std::vector<T *> &b)
{
return std::equal(a.begin(), a.end(), b.begin()
[](T* ap, T* bp) -> bool { return *ap == *bp; });
}
If you are interested in check whether there is an operator != defined in a class (and it has precisely given signature) you may want this approach (tests taken from marcinj's code):
#include <iostream>
#include <vector>
#include <type_traits>
template <class T, class = void>
struct has_not_equal: std::false_type { };
template <class T>
struct has_not_equal<T, typename std::enable_if<std::is_same<decltype(static_cast<bool (T::*)(const T&)const>(&T::operator!=)), bool (T::*)(const T&)const>::value>::type >: std::true_type { };
struct sample1{
bool operator!=(const sample1&) const { return true; }
};
struct sample2{
};
struct sample3:sample2 {
bool operator!=(const sample2& b) const { return true; }
};
struct sample4:sample2 {
bool operator!=(const sample2& b) const { return true; }
bool operator!=(const sample4& b) const { return true; }
};
int main(){
std::cout<<std::boolalpha;
std::cout<<has_not_equal<int>::value<<std::endl;
std::cout<<has_not_equal<std::string>::value<<std::endl;
std::cout<<has_not_equal<sample1>::value<<std::endl;
std::cout<<has_not_equal<sample2>::value<<std::endl;
std::cout<<has_not_equal<sample3>::value<<std::endl;
std::cout<<has_not_equal<sample4>::value<<std::endl;
}
Output of the program:
false
false
true
false
false
true
You may easily add allowed operator != overloads by adding specialization of the has_not_equal struct...
Even though the title of my question here says I'm looking for a way to determine if the != operator is defined, the real question (as you can read from the description and my comment) was how to ensure that I don't silently get wrong results for a type T which has no != operator defined. The existing answers brought up good points, but the real answer is this:
1) You will get a compiler error on the if (a[i] != b[i]) line if you instantiate the template with a value type array using a type that's missing the != operator override (e.g. std::vector<sample2> from marcinj's answer) or you get hard to understand compiler error if you use std::equal instead. In this case the explicit comparison is much more helpful when looking for a solution.
2) If you have a reference type in the vectors to compare you will at first get no problem at all (since there are comparison operators defined for references, even though they only do a flat comparison via the address values). If you want to ensure that the comparison works as if you had used value types (including deep comparison) then add a specialization for vectors with pointer types, as pointed out by Pixelchemist. However, I cannot get the std::equals variants to compile at the moment.
At the end the solution that works for me is this:
template <typename T>
static bool equals(const std::vector<T> &a, const std::vector<T> &b) {
if (a.size() != b.size())
return false;
for (size_t i = 0; i < a.size(); ++i)
if (a[i] != b[i])
return false;
return true;
}
template <typename T>
static bool equals(const std::vector<T*> &a, const std::vector<T*> &b) {
if (a.size() != b.size())
return false;
for (size_t i = 0; i < a.size(); ++i)
if (*a[i] != *b[i])
return false;
return true;
}
Tested with the sample1/2/3/4 structs from marcinj's answer. It's important to note that the operator overloading must use a value type (operator == (const sample1&)) instead of a reference type.
I have still upvoted all answers that gave me useful information to get the final answer.
If someone uses a type without overloading it might end up using a simple binary comparison which might silently lead to wrong results.
There's not such thing as "binary comparison" in C++. Either your class/struct has operator!= or the template won't be instantiated (and if there are no other candidates then it will be an error).

Comparing std::tuple (or std::pair) of custom types who has alternative orderings. Is it possible to plug-in a custom less-than / comparison function?

The Problem
I have a custom type A who has natural ordering (having operator<) and multiple alternative orderings (case-sensitive, case-insensitive, etc.). Now I have a std::pair (or std::tuple) consisting (one or more of) A. Here are some examples of types I want to compare: std::pair<A, int>, std::pair<int, A>, std::tuple<A, int, int>, std::tuple<int, A, int>. How can I compare the std::pair (or std::tuple) using the default element-wise comparison implementation, plugging-in my comparison function for A?
The Code
The code below doesn't compile:
#include <utility> // std::pair
#include <tuple> // std::tuple
#include <iostream> // std::cout, std::endl
struct A
{
A(char v) : value(v) {}
char value;
};
// LOCATION-1 (explained in the text below)
int main()
{
std::cout
<< "Testing std::pair of primitive types: "
<< (std::pair<char, int>('A', 1)
<
std::pair<char, int>('a', 0))
<< std::endl;
std::cout
<< "Testing std::tuple of primitive types: "
<< (std::tuple<char, int, double>('A', 1, 1.0)
<
std::tuple<char, int, double>('a', 0, 0.0))
<< std::endl;
// This doesn't compile:
std::cout
<< "Testing std::pair of custom types: "
<< (std::pair<A, int>('A', 1)
<
std::pair<A, int>('a', 0))
<< std::endl;
return 0;
}
It is because operator< isn't defined for struct A. Adding it to LOCATION-1 above would solve the problem:
bool operator<(A const& lhs, A const& rhs)
{
return lhs.value < rhs.value;
}
Now, we have an alternative ordering for struct A:
bool case_insensitive_less_than(A const& lhs, A const& rhs)
{
char const lhs_value_case_insensitive
= ('a' <= lhs.value && lhs.value <= 'z'
? (lhs.value + 0x20)
: lhs.value);
char const rhs_value_case_insensitive
= ('a' <= rhs.value && rhs.value <= 'z'
? (rhs.value + 0x20)
: rhs.value);
return lhs_value_case_insensitive < rhs_value_case_insensitive;
}
Supposed we want to keep the original operator< for struct A (the case-sensitive one), how can we compare std::pair<A, int> with this alternative ordering?
I know that adding a specialized version of operator< for std::pair<A, int> solves the problem:
bool operator<(std::pair<A, int> const& lhs, std::pair<A, int> const& rhs)
{
return (case_insensitive_less_than(lhs.first, rhs.first)
? true
: case_insensitive_less_than(rhs.first, lhs.first)
? false
: (lhs.second < rhs.second));
}
However, I consider this a sub-optimal solution.
Firstly, for std::pair, it is easy to re-implement the element-wise comparison, but for std::tuple it might be complicated (dealing with variadic templates) and error-prone.
Secondly, I can hardly believe that it is the best-practice way to solve the problem: imagine that we have to define a specialized version of operator< for each of the following classes: std::tuple<A, int, int>, std::tuple<int, A, int>, std::tuple<int, int, A>, std::tuple<A, A, int>, ... (It's not even a practical way!)
Re-using the well written built-in operator< for std::tuple and plugging-in my less-than for struct A would be what I want. Is it possible? Thanks in advance!
The easy way would be to manually write compare( tup, tup, f ) that uses f to lexographically compare the elements in the tuples. But that is boring.
// This type wraps a reference of type X&&
// it then overrides == and < with L and E respectively
template<class X, class L, class E>
struct reorder_ref {
using ref = reorder_ref;
X&& x;
friend bool operator<(ref lhs, ref rhs) {
return L{}((X&&) lhs.x, (X&&) rhs.x);
}
friend bool operator==(ref lhs, ref rhs) {
return E{}((X&&) lhs.x, (X&&) rhs.x);
}
// other comparison ops based off `==` and `<` go here
friend bool operator!=(ref lhs, ref rhs){return !(lhs==rhs);}
friend bool operator>(ref lhs, ref rhs){return rhs<lhs;}
friend bool operator<=(ref lhs, ref rhs){return !(lhs>rhs);}
friend bool operator>=(ref lhs, ref rhs){return !(lhs<rhs);}
reorder_ref(X&& x_) : x((X&&) x_) {}
reorder_ref(reorder_ref const&) = default;
};
the above is a reference that changes how we order.
// a type tag, to pass a type to a function:
template<class X>class tag{using type=X;};
// This type takes a less than and equals stateless functors
// and takes as input a tuple, and builds a tuple of reorder_refs
// basically it uses L and E to compare the elements, but otherwise
// uses std::tuple's lexographic comparison code.
template<class L, class E>
struct reorder_tuple {
// indexes trick:
template<class Tuple, class R, size_t... Is>
R operator()(tag<R>, std::index_sequence<Is...>, Tuple const& in) const {
// use indexes trick to do conversion
return R( std::get<Is>(in)... );
}
// forward to the indexes trick above:
template<class... Ts, class R=std::tuple<reorder_ref<Ts const&, L, E>...>>
R operator()(std::tuple<Ts...> const& in) const {
return (*this)(tag<R>{}, std::index_sequence_for<Ts...>{}, in);
}
// pair filter:
template<class... Ts, class R=std::pair<reorder_ref<Ts const&, L, E>...>>
R operator()(std::pair<Ts...> const& in) const {
return (*this)(tag<R>{}, std::index_sequence_for<Ts...>{}, in);
}
};
the above stateless function object takes some new less and equals operations, and maps any tuple to a tuple of reorder_ref<const T, ...>, which change the ordering to follow L and E respectively.
This next type does what std::less<void> does for std::less<T> sort of -- it takes a type-specific stateless ordering function template object, and makes it a type-generic stateless ordering function object:
// This takes a type-specific ordering stateless function type, and turns
// it into a generic ordering function type
template<template<class...> class order>
struct generic_order {
template<class T>
bool operator()(T const& lhs, T const& rhs) const {
return order<T>{}(lhs, rhs);
}
};
so if we have a template<class T>class Z such that Z<T> is an ordering on Ts, the above gives you a universal ordering on anything.
This next one is a favorite of mine. It takes a type T, and orders it based on a mapping to a type U. This is surprisingly useful:
// Suppose there is a type X for which we have an ordering L
// and we have a map O from Y->X. This builds an ordering on
// (Y lhs, Y rhs) -> L( O(lhs), O(rhs) ). We "order" our type
// "by" the projection of our type into another type. For
// a concrete example, imagine we have an "id" structure with a name
// and age field. We can write a function "return s.age;" to
// map our id type into ints (age). If we order by that map,
// then we order the "id" by age.
template<class O, class L = std::less<>>
struct order_by {
template<class T, class U>
bool operator()(T&& t, U&& u) const {
return L{}( O{}((T&&) t), O{}((U&&) u) );
}
};
Now we glue it all together:
// Here is where we build a special order. Suppose we have a template Z<X> that returns
// a stateless order on type X. This takes that ordering, and builds an ordering on
// tuples based on it, using the above code as glue:
template<template<class...>class Less, template<class...>class Equals=std::equal_to>
using tuple_order = order_by< reorder_tuple< generic_order<Less>, generic_order<Equals> > >;
tuple_order does most of the work for us. All we need is to provide it with an element-wise ordering template stateless function object. tuple_order will then produce a tuple ordering functor based on it.
// Here is a concrete use of the above
// my_less is a sorting functiont that sorts everything else the usual way
// but it sorts Foo's backwards
// Here is a toy type. It wraps an int. By default, it sorts in the usual way
struct Foo {
int value = 0;
// usual sort:
friend bool operator<( Foo lhs, Foo rhs ) {
return lhs.value<rhs.value;
}
friend bool operator==( Foo lhs, Foo rhs ) {
return lhs.value==rhs.value;
}
};
template<class T>
struct my_less : std::less<T> {};
// backwards sort:
template<>
struct my_less<Foo> {
bool operator()(Foo const& lhs, Foo const& rhs) const {
return rhs.value < lhs.value;
}
};
using special_order = tuple_order< my_less >;
and bob is your uncle (live example).
special_order can be passed to a std::map or std::set, and it will order any tuples or pairs encountered with my_less replacing the default ordering of the elements.