Make standard Tuple interface extensible to user defined types

202 views
Skip to first unread message

Tomasz

unread,
Aug 9, 2015, 2:33:06 AM8/9/15
to ISO C++ Standard - Future Proposals
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

David Krauss

unread,
Aug 9, 2015, 7:04:19 AM8/9/15
to std-pr...@isocpp.org

On 2015–08–09, at 2:33 PM, Tomasz <toma...@gmail.com> wrote:

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

ADL is invoked, as long as a template declaration is visible.

namespace my_project {
    template< typename >
    void get() = delete;

    std::tuple< int > blah;
    int q = get< 0 >( blah );
}


The problem is that it’s not idiomatic to call get without namespace qualification.

I think this is just one aspect of the more general problem with ADL and idiomatically writing std:: which applies to many free functions. There has been discussion here with various solutions such as introducing namespace adl. It occurs to me that std::swap, std::get, std::static_pointer_cast, etc could be defined to use ADL. This solves the problem at least up to having a common template signature — there’s no way to call std::get<index>(obj) and still pass a non-type index argument of user-defined type.

One possible implementation:

// Helper; could be folded into members of tuple, pair, and array.
template< typename >
struct __is_tuple_like : false_type {};

template< typename ... t >
struct __is_tuple_like< tuple< t ... > > : true_type {};

// Stop ADL from causing infinite template recursion.
template< bool >
struct __get_lookup;

template<>
struct __get_lookup< false > {
    template< size_t i, typename t >
    using result_type = decltype( get< i >( declval< t >() ) );
};

// Overload enables std::get syntax to find user's ADL.
template< size_t i, typename t >
typename __get_lookup< __is_tuple_like< decay_t< t > >::value >::template result_type< i, t >
get( t && o )
    { return get< i >( forward< t >( o ) ); }


Repeat this pattern for anything else with a generic-ish free function interface.

Reply all
Reply to author
Forward
0 new messages