0

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

  1. 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

  1. 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.

  1. 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

  1. 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

  1. 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

  1. defining conversion between types and handle only one type per operator
    #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.

Sir Demios
  • 83
  • 11
  • 1
    *"2. to add cast operators to wrapper"*. It is only needed because *you expect* *"unintentional implicit conversion"*, [Example 2 modified](https://godbolt.org/z/1EhacT7qY). – Jarod42 Aug 26 '22 at 23:39
  • *"for every type combination"*. You might use projection, something like `template bool operator()(const U& lhs, const U rhs) const { Proj(lhs) < Proj(rhs); } int Proj(const T& t) const { return t.val; } int Proj(int n) const { return n; }` – Jarod42 Aug 26 '22 at 23:47
  • @Jarod42 I didn't even see that i was doing that in the for loop. Cool, so that means it works the way I expected it to, I was just asking it something else that was unreasonable. – Sir Demios Aug 29 '22 at 17:44

0 Answers0