In general, tuple should be used in generic code.
If you know field 1 is a Number or a Chicken, you shouldn't be using a tuple. You should be using a struct with a field called Number.
If you need tuple-like functionality (as one does), you can simply write as_tie:
struct SomeType {
int Number;
std::string Chicken;
auto as_tie() { return std::tie(Number, Chicken); }
auto as_tie() const { return std::tie(Number, Chicken); }
};
Now you can access SomeType as a tuple of references by typing someInstance.as_tie().
This still doesn't give you < or == etc for free. We can do that in one place and reuse it everywhere you use the as_tie technique:
struct as_tie_ordering {
template<class T>
using enable = std::enable_if_t< std::is_base_of<as_tie_ordering, std::decay_t<T>>, int>;
template<class T, enable<T> =0>
friend bool operator==(T const& lhs, T const& rhs) {
return lhs.as_tie() == rhs.as_tie();
}
template<class T, enable<T> =0>
friend bool operator!=(T const& lhs, T const& rhs) {
return lhs.as_tie() != rhs.as_tie();
}
template<class T, enable<T> =0>
friend bool operator<(T const& lhs, T const& rhs) {
return lhs.as_tie() < rhs.as_tie();
}
template<class T, enable<T> =0>
friend bool operator<=(T const& lhs, T const& rhs) {
return lhs.as_tie() <= rhs.as_tie();
}
template<class T, enable<T> =0>
friend bool operator>=(T const& lhs, T const& rhs) {
return lhs.as_tie() >= rhs.as_tie();
}
template<class T, enable<T> =0>
friend bool operator>(T const& lhs, T const& rhs) {
return lhs.as_tie() > rhs.as_tie();
}
};
which gives us:
struct SomeType:as_tie_ordering {
int Number;
std::string Chicken;
auto as_tie() { return std::tie(Number, Chicken); }
auto as_tie() const { return std::tie(Number, Chicken); }
};
and now
SomeTime a,b;
bool same = (a==b);
works. Note that as_tie_ordering doesn't use CRTP and is an empty stateless class; this technique uses Koenig lookup to let instances find the operators.
You can also implement an ADL-based get
struct as_tie_get {
template<class T>
using enable = std::enable_if_t< std::is_base_of<as_tie_get, std::decay_t<T>>, int>;
template<std::size_t I, class T,
enable<T> =0
>
friend decltype(auto) get( T&& t ) {
using std::get;
return get<I>( std::forward<T>(t).as_tie() );
}
};
Getting std::tuple_size to work isn't as easy, sadly.
The enable<T> =0 clauses above should be replaced with class=enable<T> in MSVC, as their compiler is not C++11 compliant.
You'll note above I use tuple; but I'm using it generically. I convert my type to a tuple, then use tuple's < to write my <. That glue code deals with tie as a generic bundle of types. That is what tuple is for.