无法对用户定义的类型使用 std::apply
Unable to use std::apply on user-defined types
在为我正在进行的某个项目实施 compressed_tuple
class 时,我 运行 遇到以下问题:我似乎无法将此类型的实例传递给std::apply,尽管根据 https://en.cppreference.com/w/cpp/utility/apply.
这应该是可能的
我使用以下片段 (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);
当我尝试编译这段代码时,它似乎找不到我定义的 std::get
重载,即使它们应该完全匹配。相反,它会尝试匹配所有其他重载(std::get(pair)、std::get(array<...>) 等),甚至不提及我的重载.我在所有三个主要编译器(MSVC、Clang、GCC)中都遇到了一致的错误。
所以我的问题是这是否是预期的行为并且根本不可能将 std::apply
用于用户定义的类型?有解决办法吗?
So my question is whether this is expected behavior and it's simply
not possible to use std::apply with user-defined types?
不行,目前没有办法。
在 libstdc++、libc++ 和 MSVC-STL 实现中,std::apply
在内部使用 std::get
而不是不合格的 get
,因为禁止用户在 [ 下定义 get
=14=],无法将 std::apply
应用于用户自定义类型。
你可能会问,在[tuple.creation]中,标准是这样描述tuple_cat
的:
[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]
这是否表明其他 tuple
实用函数(例如 std::apply
应该支持用户定义的 tuple
类类型?
请特别注意,术语“tuple
-like”在
这个时间点。这是故意留给 C++ 委员会的,以弥补这一差距
由未来的提案填补。有一个提案是
打算着手改进这件事,见P2165R3.
And is there a work-around?
在采用 P2165 之前,不幸的是,您可能必须实现自己的 apply
并使用不合格的 get
。
问题已被@康桐薇完整解答。我将 post 有关如何提供解决方法的更多详细信息。
首先,这是一个通用的 C++20 apply
函数:
#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
自己的命名空间很有用,这样自定义应用就不会干扰 std-version。对 get
的不合格调用意味着(引用他的 blog 中的@Quuxplusone,这是迄今为止我遇到的最好的解释):
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.”
然后您可以推出自己的 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;
};
}
通过 get()
的重载和 std::tuple_size
的特化,应用函数将按预期工作。此外,您可以插入任何兼容的 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>{});
}
在为我正在进行的某个项目实施 compressed_tuple
class 时,我 运行 遇到以下问题:我似乎无法将此类型的实例传递给std::apply,尽管根据 https://en.cppreference.com/w/cpp/utility/apply.
我使用以下片段 (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);
当我尝试编译这段代码时,它似乎找不到我定义的 std::get
重载,即使它们应该完全匹配。相反,它会尝试匹配所有其他重载(std::get(pair
所以我的问题是这是否是预期的行为并且根本不可能将 std::apply
用于用户定义的类型?有解决办法吗?
So my question is whether this is expected behavior and it's simply not possible to use std::apply with user-defined types?
不行,目前没有办法。
在 libstdc++、libc++ 和 MSVC-STL 实现中,std::apply
在内部使用 std::get
而不是不合格的 get
,因为禁止用户在 [ 下定义 get
=14=],无法将 std::apply
应用于用户自定义类型。
你可能会问,在[tuple.creation]中,标准是这样描述tuple_cat
的:
[Note 1: An implementation can support additional types in the template parameter pack
Tuples
that support thetuple
-like protocol, such aspair
andarray
. — end note]
这是否表明其他 tuple
实用函数(例如 std::apply
应该支持用户定义的 tuple
类类型?
请特别注意,术语“tuple
-like”在
这个时间点。这是故意留给 C++ 委员会的,以弥补这一差距
由未来的提案填补。有一个提案是
打算着手改进这件事,见P2165R3.
And is there a work-around?
在采用 P2165 之前,不幸的是,您可能必须实现自己的 apply
并使用不合格的 get
。
问题已被@康桐薇完整解答。我将 post 有关如何提供解决方法的更多详细信息。
首先,这是一个通用的 C++20 apply
函数:
#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
自己的命名空间很有用,这样自定义应用就不会干扰 std-version。对 get
的不合格调用意味着(引用他的 blog 中的@Quuxplusone,这是迄今为止我遇到的最好的解释):
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.”
然后您可以推出自己的 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;
};
}
通过 get()
的重载和 std::tuple_size
的特化,应用函数将按预期工作。此外,您可以插入任何兼容的 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>{});
}