What are the requirements to ensure that type equivalence can be used in the find function of a sorted container? Specifically how does mixed type comparison work.
Setup
Say I've got a struct, comparator, and set
struct Wrapper{int val;};
template<class T> struct compare {...};
template<class T> using wset=set<T,compare<T>>;
and a main function
int main() {
wset<Wrapper> s;
s.insert(Wrapper{1}); //direct insertion
s.insert(Wrapper{10});
s.insert(11); //insertion by implicit construction
s.insert(100);
for (int x : s)
cout << x << ' ';
auto it= s.find(10);//find by equivalence
cout<<*it;
return 0;
}
I can get the code to run by adding constructors and conversion overloads to the wrapper struct, and also adding parameterized binary predicates to the compare struct. But I don't understand how its actually working.
Equivalence Implementation
From my understanding find(a) will be implemented by searching the set for an object b that satisfies !(a<b)&&!(b<a). therefore multi type equivalence should only require defining Ta<Tb and Tb<Ta ie
bool operator()(Ta a, Tb b){...}
bool operator()(Tb b, Ta a){...}
but in practice the compiler fails to make that comparison if that's all it has to go on.
Possible Options
Its seems like my only options are to
- let the wrapper object be constructed by the incoming type
struct Wrapper{
Wrapper(int val):val{val}{};
int val;
};
template<class T>
struct compare {
bool operator() (T const & a, T const & b) const {return a.val < b.val;}
};
which seems like it has an unnecessary object being constructed
- to add cast operators to wrapper and the operators to compare mixed types
struct Wrapper
{
int val;
operator int()const {return val;};
};
template<class T>
struct compare {
using is_transparent=true_type;
bool operator() (T const & a, T const & b) const {return a.val < b.val;}
bool operator() (T const & a, int const & b) const {return a.val < b;}
bool operator() (int const & a, T const & b) const {return a < b.val;}
};
which may allow unintentional implicit conversion in other places.
- to add cast operators to wrapper, and an operator to compare one type at time.
struct Wrapper{
int val;
operator int()const {return val;};
};
template<class T>
struct compare {
using is_transparent=true_type;
bool operator() (T const & a, T const & b) const {return a.val < b.val;}
bool operator() (int const & a, int const & b) const {return a < b;}
};
which like 2 may allow unintentional implicit conversion in other places.
Working examples
- using a constructor, implicit conversion, and a less than operator to handle multiple types on insert and in find
#include <iostream>
#include <set>
using namespace std;
struct Wrapper{
Wrapper(int val):val{val}{};
int val;
operator int()const {return val;};
};
template<class T>
struct compare {
bool operator() (T const & a, T const & b) const {return a.val < b.val;}
};
template<class T>
using wset=set<T,compare<T>>;
int main() {
wset<Wrapper> s;
s.insert(Wrapper{1});
s.insert(Wrapper{10});
s.insert(11);
s.insert(100);
for (int x : s)
cout << x << ' ';
auto it= s.find(10);
cout<<*it;
return 0;
}
this works but it feels like its probably constructing a wrapper in order to do the find
- using implicit conversion, parameterizing the comparator operators, and making the comparator transparent prevents unintentional construction
#include <iostream>
#include <set>
using namespace std;
struct Wrapper{
int val;
operator int()const {return val;};
};
template<class T>
struct compare {
using is_transparent=true_type;
bool operator() (T const & a, T const & b) const {return a.val < b.val;}
bool operator() (T const & a, int const & b) const {return a.val < b;}
bool operator() (int const & a, T const & b) const {return a < b.val;}
};
template<class T>
using wset=set<T,compare<T>>;
int main() {
wset<Wrapper> s;
s.insert(Wrapper{1});
s.insert(Wrapper{10});
s.insert(Wrapper{11});
s.insert(Wrapper{100});
for (int x : s)
cout << x << ' ';
auto it= s.find(10);
cout<<*it;
return 0;
}
but requires that for every type combination there is a converter from WT to T and a operator for both (WT,T) and (T,WT). The converter also can't be made explicit so it exposes potential accidental conversion
#include <iostream>
#include <set>
using namespace std;
struct Wrapper{
int val;
operator int()const {return val;};
};
template<class T>
struct compare {
using is_transparent=true_type;
bool operator() (T const & a, T const & b) const {return a.val < b.val;}
bool operator() (int const & a, int const & b) const {return a < b;}
};
template<class T>
using wset=set<T,compare<T>>;
int main() {
wset<Wrapper> s;
s.insert(Wrapper{1});
s.insert(Wrapper{10});
s.insert(Wrapper{11});
s.insert(Wrapper{100});
for (int x : s)
cout << x << ' ';
auto it= s.find(10);
cout<<*it;
return 0;
}
which is simpler than option 2 but still relies on implicit conversion.