Я написал некоторый общий код, который управляет списком кортежей. Теперь я хочу использовать этот код, но вместо 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
должна быть свободной функцией в пространстве имен класса. Это позволяет расширять типы, которые пишут другие люди.
Далее вы должны попытаться позвонить get
в контексте с поддержкой ADL.
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_size
а также tuple_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::get
или другой get
в x
пространство имен, основанное на правилах разрешения перегрузки.
Мы можем создать похожих помощников:
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
звонок, который получает размер Foo
либо (A) вызывая tuple_size( tag_t<Foo> )
в контексте с поддержкой ADL или (B) возвращая std::tuple_size<Foo>::value
,
Получив это, мы можем создать еще один базовый тип помощника:
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
код, а также будет работать с типами, унаследованными от as_tuple_helper
который имеет friend as_tuple
определены.
Это, однако, не дает вам поддержки структурированных привязок.