Search code examples
c++templatesc++17inner-classesstructured-bindings

Tuple-like structured binding for an inner class of a class template


I want to provide structured binding for an inner class of a class template. How can I specialise std::tuple_size for that inner class?

I can't use structured binding to data member, because the inner class may be a type incompatible with that feature. So I need to provide tuple-like structured binding. To provide such a feature to the inner class I need to partially specialise std::tuple_size in namespace std. The problem is that I got a non-deduced context for the parameter T (of the outer class).

I know that I could put the inner class in the global namespace and thus solve every problem, but is there any way to get the same result keeping the class inner?

#include <tuple>

template<typename T>
class Collection
{
    public:
        struct Element
        {
            int id;
            T actual_element;
        };

    //...
};


namespace std 
{
    template<typename T>
    struct tuple_size<typename Collection<T>::Element> // Error! Non-deduced context
        : std::integral_constant<std::size_t, 2> {};
}



//Collection iterators...

int main()
{
    Collection<int> collection;

    for (auto & [id, element] : collection) //what I'd like to achieve
        //do some stuff...
}

Solution

  • You don't have to provide a binding for this case: Element is already decomposable as is:

    struct Element { int i, j; };
    auto [i, j] = Element{2, 3}; // ok
    

    However, assuming Element is actually more complex and needs custom bindings, then yes - you will need to move it out. However, it doesn't need to be in a global namespace. It could be somewhere else:

    namespace detail {
        template <typename T> struct Element { ... };
    }
    
    template<typename T>
    class Collection
    {
        public:
            using Element = detail::Element<T>;
            friend Element;
            // ...
    };
    

    And at that point, specializing the bindings is straightforward. There is no way around that since, as you point out, specializing on Collection<T>::Element is a non-deduced context.


    Short of a new language feature that would let you opt-in to structured bindings within the class body itself. There was such a paper for this, P1096, but it was rejected when presented. Which isn't to say a new proposal couldn't do better.