使结构的行为类似于 std::tuple



我写了一些管理元组列表的通用代码。现在我想使用该代码,但我想使用简单的结构而不是std::tuple,这样我就可以使用名称而不是指示来访问变量。有没有一种简单的方法可以使这些结构的行为像std::tuple,以便我可以将其与我的泛型代码一起使用?

struct foo {
int x;
float y;
// some code to enable tuple like behavior (e.g. std::get, std::tuple_size)
};

我尝试添加一个as_tuple成员函数,该函数使用std::tie返回所有成员。这有效,但需要在我需要元组行为的所有地方调用此成员函数。

手动方式:

struct foo {
int x;
float y;
};

namespace std
{
template <>
class tuple_element<0, foo> {
using type = int;
};
template <>
class tuple_element<1, foo> {
using type = float;
};
template <std::size_t I>
tuple_element_t<I, foo>& get(foo&);
template <>
tuple_element_t<0, foo>& get(foo& f) { return f.x;}
template <>
tuple_element_t<1, foo>& get(foo& f) { return f.y; }
template <std::size_t I>
tuple_element_t<I, foo> get(const foo&);
template <>
tuple_element_t<0, foo> get(const foo& f) { return f.x;}
template <>
tuple_element_t<1, foo> get(const foo& f) { return f.y; }
}

另一种方法是as_tuple编写函数:

template <typename ... Ts>
std::tuple<Ts...>& as_tuple(std::tuple<Ts...>& tuple) { return tuple; }
std::tuple<int&, float&> as_tuple(foo& f) { return std::tie(f.x, f.y); }

并在使用类似元组之前包装您的呼叫。

首先,as_tuple应该是类命名空间中的自由函数。 这使您可以扩展其他人编写的类型。

接下来,应尝试在启用了 ADL 的上下文中调用get

using std::get;
auto& x = get<1>(foo);

如果你这样做,我们可以发挥一些魔力。

struct get_from_as_tuple {
template<std::size_t I,
class T,
std::enable_if_t< std::is_base_of< get_from_as_tuple, std::decay_t<T> >, bool > = true
>
friend decltype(auto) get( T&& t ) {
return std::get<I>( as_tuple( std::forward<T>(t) ) );
}
};

现在

struct foo:get_from_as_tuple {
int x;
float y;
friend auto as_tuple( get_from_as_tuple const& self ) {
return std::tie( self.x, self.y );
}
};

我们可以这样做:

foo f;
using std::get;
std::cout << get<0>(f) << "," << get<1>(f) << "n";

现在,这仍然无法启用tuple_sizetuple_element

没有微不足道的方法可以完成这一部分,但我们可以解决它。

#define RETURNS(...) 
noexcept(noexcept(__VA_ARGS__)) 
-> decltype(__VA_ARGS__) 
{ return __VA_ARGS__; }
namespace tup {
namespace adl_get {
using std::get;
template<std::size_t I,
class T
>
auto get_helper( T&& t )
RETURNS( get<I>(std::forward<T>(t) ) )
}
template<std::size_t I, class T>
auto get( T&& t )
RETURNS(adl_get::get_helper<I>(std::forward<T>(t)))
}

现在tup::get<7>( x )将根据重载解析规则调度到std::getx命名空间中的其他get

我们可以创建类似的帮助程序:

namespace util {
template<class T>
struct tag_t {constexpr tag_t(){}};
template<class T>
constexpr tag_t<T> tag{};
}
namespace tup {
namespace adl_tuple_size {
template<class T>
constexpr std::size_t get_tuple_size( tag_t<T>, ... ) {
return std::tuple_size<T>::value;
}
template<class T>
constexpr auto get_tuple_size( tag_t<T>, int )
RETURNS( tuple_size( tag_t<T> ) )
}
template<class T>
constexpr std::size_t tuple_size() {
return adl_tuple_size::get_tuple_size( tag<T> );
}
}

现在tup::tuple_size<Foo>()是一个constexpr调用,它通过 (A) 在启用 ADL 的上下文中调用tuple_size( tag_t<Foo> )或 (B) 返回std::tuple_size<Foo>::value来获取Foo的大小。

有了这个,我们可以创建另一个辅助程序基类型:

struct tuple_size_from_as_tuple {
template<std::size_t I,
class T,
std::enable_if_t< std::is_base_of< get_from_as_tuple, std::decay_t<T> >, bool > = true
>
friend std::size_t tuple_size( T&& t ) {
return std::tuple_size< decltype(as_tuple( std::forward<T>(t) ) ) >::value;
}
};
struct as_tuple_helpers : get_from_as_tuple, tuple_size_from_as_tuple {};
struct foo:as_tuple_helpers {
// ..
};

我们现在有 2 个原语。

tag_t<E&> tuple_element( tag_t<T> )重复此操作。 然后我们可以编写一个tup::tuple_element<T, 0>别名,随心所欲地调度。

最后,调整与std::元组工具一起使用的现有代码以使用tup::工具。 它应该与现有的tuple代码一起使用,并且还将处理从定义了friend as_tupleas_tuple_helper继承的类型。

但是,这不会为您提供对结构化绑定的支持。

最新更新