Currently, the standard interface for tuple-like types, consists of tree members:
- trait std::tuple_size<T>
- trait std::tuple_element<I, T>
- function template std::get<I>(T)
If I am writing my own tuple_like type, for e.g. compressed_type and I want it to behave as a standard tuple, I would need to do following:
- specialize tuple_size
- specialize tuple_element
- add new overload for std::get
The first two items are fine, but third one seems to be no-go. The standard prohibit extension of the std namespace with new overloads, so I cannot add overload of get for compressed pair (function template cannot be partially specialized). Furthermore, the standard solution to declare the get method in my class namsepace and use ADL will not work, because ADL is not invoked for function template class with explicitly provided argument (index).
To resolve the problem I propose that std::get<I>(t) definition should be changed to invoke tuple_get(std::integral_constant<std::size_t, I>{}, t), where tuple_get is defined as customization point, i.e. std::get is required to find of overload tuple_get via ADL. The benefits of the solution:
1. It makes the tuple-like interface extensible for user defined classes - to make class a tuple I need to specialize tuple_size, tuple_element and provide tuple_get overload in my own namespace. Instead of passing index as non-type template argument, it is passed used integral_constant argument to allows use of ADL.
2. It makes the implementation of tuple-interface easier. Instead of writing the SFINAEd overload for index zero and non zero:
template<std::size_t I, typename T>
enable_if_t<I == 0, tuple_element_t<I, decay_t<I>>> const& get(T const& t);
template<std::size_t I, typename T>
enable_if_t<I != 0, tuple_element_t<I, decay_t<I>>> const& get(T const& t);
I can just rely on the partial template ordering.
tuple_element_t<0, decay_t<I>> const& tuple_get(integral_constant<std::size_t, 0>, T const&);
//Zero case, will be used as more specialzied
template<std::size_t I, typename T>
tuple_element_t<I, decay_t<I>> const& tuple_get(integral_constant<std::size_t, I>, T const&)
3. The type access for the tuple should be implemented using the tuple interface (tuple_size_t, tuple_element_t, tuple_get), so it will be automatically provided for any user-defined class that meats tuple requirements.
template<typename T, typename Tuple, typename = enable_if_t<only_one<T, std::decay_t<Tuple>>::value>>
T const& get(Tuple const&) { return tuple_get(index_of<T, std::decay_t<Tuple>>{}, t); }
only_one<T, std::decay_tuple_t<tuple>>::value is expected to return true if there is only one occurrence of type T in tuple, index_of returns first index of type T (in form of integral_consant<std::size_t, I>). Both can rely on use of only tuple_element and tuple_size traits.
Regards,
Tomasz Kamiński