Skip to content

Instantly share code, notes, and snippets.

@mclow
Created October 26, 2021 21:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mclow/7c9c0eab6bd3bdc6d71bd956ecf4dd3c to your computer and use it in GitHub Desktop.
Save mclow/7c9c0eab6bd3bdc6d71bd956ecf4dd3c to your computer and use it in GitHub Desktop.
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <string>
#include <tuple>
using namespace std;
struct A { int i; }; bool operator<(const A&x, const A&y) { return x.i < y.i; }
struct B { float f; }; bool operator<(const B&x, const B&y) { return x.f < y.f; }
struct C { char c; }; bool operator<(const C&x, const C&y) { return x.c < y.c; }
typedef tuple<A, B, C> TABC;
typedef tuple<A, B> TAB;
struct TupCompare {
typedef void is_transparent;
bool operator() (const TABC &x, const TABC &y) const
{ return x < y; }
bool operator() (const TABC &x, const TAB &y) const {
if (get<0>(x) < get<0>(y)) return true;
if (get<0>(y) < get<0>(x)) return false;
if (get<1>(x) < get<1>(y)) return true;
if (get<1>(y) < get<1>(x)) return false;
return false;
}
bool operator() (const TAB &x, const TABC &y) const {
if (get<0>(x) < get<0>(y)) return true;
if (get<0>(y) < get<0>(x)) return false;
if (get<1>(x) < get<1>(y)) return true;
if (get<1>(y) < get<1>(x)) return false;
return false;
}
};
int main () {
set<TABC, TupCompare> s2;
s2.emplace(A{1}, B{2.f}, C{'3'});
s2.emplace(A{1}, B{2.f}, C{'4'});
s2.emplace(A{1}, B{2.f}, C{'5'});
s2.emplace(A{4}, B{5.f}, C{'6'});
for (const auto el: s2)
cout << get<0>(el).i << ' ' << get<1>(el).f << ' ' << get<2>(el).c << endl;
auto eq = s2.equal_range(TAB(A{1}, B{2.f}));
cout << distance(eq.first, eq.second) << endl;
}
@matgrioni
Copy link

This is a neat idea, but unfortunately it doesn't work out in practice for one notable flaw. The issue comes from the fact that TupCompare basically says that Tup<A, B> can be equal to Tup<A, B, C>. If you do find with a Tup<A, B> instance you will get an actual value found as opposed to the desired missing entry (since there's no entry associated with a Tup<A, B> instance).

One workaround is to use std::equal_range which can take in a custom comparison function, but obviously that's not as efficient as actually knowing the internal map data structure since it is a red-black tree usually. The implementations would be similar asymptotically I believe, but do differ.

But as for being able to do heterogenous lookup on the map with equal_range via a subset of the key, it does not seem it can be done without that other side effect.

@mclow
Copy link
Author

mclow commented Oct 23, 2023

I disagree - it does work in practice. But - as you say - this also affects the results of calls like find.

The surprising thing to me was in line 51, where equal_range returned a range that had more than one element in it, even though this is a set, which contains unique entries.

Is it useful? That's a different question.

@matgrioni
Copy link

In the heat of writing a comment, I did overstep in saying it doesn't work in practice. If it works for a scenario depends on the scenario.

The important point though is that there is a very critical change in behavior to operations like find and it's a type of error that is unexpected (usually it's not a concern if you passed in the right type of lookup key) and can never be flagged by a compiler. With no warning on the code sample or examples of issues, it feels like it's presented as a free lunch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment