c++tuplesc++17stdapply

Unable to use std::apply on user-defined types


While implementing a compressed_tuple class for some project I'm working on, I ran into the following issue: I can't seem to pass instances of this type to std::apply, even though this should be possible according to: https://en.cppreference.com/w/cpp/utility/apply.

I managed to reproduce the issue quite easily, using the following fragment (godbolt):

#include <tuple>

struct Foo {
public:
    explicit Foo(int a) : a{ a } {}

    auto &get_a() const { return a; }
    auto &get_a() { return a; }

private:
    int a;
};

namespace std {

template<>
struct tuple_size<Foo> {
    constexpr static auto value = 1;
};

template<>
struct tuple_element<0, Foo> {
    using type = int;
};

template<size_t I>
constexpr auto get(Foo &t) -> int & {
    return t.get_a();
}

template<size_t I>
constexpr auto get(const Foo &t) -> const int & {
    return t.get_a();
}

template<size_t I>
constexpr auto get(Foo &&t) -> int && {
    return std::move(t.get_a());
}

template<size_t I>
constexpr auto get(const Foo &&t) -> const int && {
    return move(t.get_a());
}

} // namespace std

auto foo = Foo{ 1 };
auto f = [](int) { return 2; };

auto result = std::apply(f, foo);

When I try to compile this piece of code, it seems that it cannot find the std::get overloads that I have defined, even though they should perfectly match. Instead, it tries to match all of the other overloads (std::get(pair<T, U>), std::get(array<...>), etc.), while not even mentioning my overloads. I get consistent errors in all three major compilers (MSVC, Clang, GCC).

So my question is whether this is expected behavior and it's simply not possible to use std::apply with user-defined types? And is there a work-around?


Solution

  • So my question is whether this is expected behavior and it's simply not possible to use std::apply with user-defined types?

    No, there is currently no way.

    In libstdc++, libc++, and MSVC-STL implementations, std::apply uses std::get internally instead of unqualified get, since users are prohibited from defining get under namespace std, it is impossible to apply std::apply to user-defined types.

    You may ask, in [tuple.creation], the standard describes tuple_cat as follows:

    [Note 1: An implementation can support additional types in the template parameter pack Tuples that support the tuple-like protocol, such as pair and array. — end note]

    Does this indicate that other tuple utility functions such as std::apply should support user-defined tuple-like types?

    Note that in particular, the term "tuple-like" has no concrete definition at this point of time. This was intentionally left C++ committee to make this gap being filled by a future proposal. There exists a proposal that is going to start improving this matter, see P2165R3.


    And is there a work-around?

    Before P2165 is adopted, unfortunately, you may have to implement your own apply and use non-qualified get.