'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 1:[1]
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 thetuple
-like protocol, such aspair
andarray
. — 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
.
Solution 2:[2]
The question has throughly been answered by @???. I'm going to post some more details on how to provide a workaround.
First, here is a generic C++20 apply
function:
#include<tuple>
#include<functional>
namespace my
{
constexpr decltype(auto) apply(auto&& function, auto&& tuple)
{
return []<size_t ... I>(auto && function, auto && tuple, std::index_sequence<I...>)
{
using std::get;
return std::invoke(std::forward<decltype(function)>(function)
, get<I>(std::forward<decltype(tuple)>(tuple)) ...);
}(std::forward<decltype(function)>(function)
, std::forward<decltype(tuple)>(tuple)
, std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<decltype(tuple)> > >{});
}
} //namespace my
The own namespace is useful so that the custom apply does not interfere with the std-version. The unqualified call to get
means (quoting @Quuxplusone from his blog, which gives the best explanation I encountered so far):
An unqualified call using the two-step, like using my::xyzzy; xyzzy(t), indicates, “I know one way to xyzzy whatever this thing may be, but T itself might know a better way. If T has an opinion, you should trust T over me.”
You can then roll out your own tuple-like class,
struct my_tuple
{
std::tuple<int,int> t;
};
template<size_t I>
auto get(my_tuple t)
{
return std::get<I>(t.t);
}
namespace std
{
template<>
struct tuple_size<my_tuple>
{
static constexpr size_t value = 2;
};
}
With the overload of get()
and the specialization of std::tuple_size
, the apply function then works as expected. Moreover, you can plug in any compliant std-type:
int main()
{
auto test = [](auto ... x) { return 1; };
my::apply(test, my_tuple{});
my::apply(test, std::tuple<int,double>{});
my::apply(test, std::pair<int,double>{});
my::apply(test, std::array<std::string,10>{});
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | davidhigh |