Кортежи C ++ 11 хороши, но у меня есть два больших недостатка: доступ к членам по индексу
По сути, я хочу добиться этого
tagged_tuple <name, std::string, age, int, email, std::string> get_record (); {/*...*/}
// And then soomewhere else
std::cout << "Age: " << get_record().get <age> () << std::endl;
Нечто подобное (маркировка типов) реализовано в boost :: property_map, но я не могу понять, как реализовать это в кортеже с произвольным числом элементов
PS
Пожалуйста, сделай не предложить определить перечисление с индексами элементов кортежа.
UPD
Хорошо, вот мотивация. В моих проектах мне нужно иметь возможность определять множество различных кортежей «на лету», и все они должны иметь определенные общие функции и операторы. Этого невозможно достичь с помощью структур
UPD2
На самом деле мой пример, возможно, немного нереалистичен для реализации. Как насчет этого?
tagged_tuple <tag<name, std::string>, tag<age, int>, tag<email, std::string>> get_record (); {/*...*/}
// And then soomewhere else
std::cout << "Age: " << get_record().get <age> () << std::endl;
Я не знаю ни одного существующего класса, который делает это, но довольно легко собрать что-то вместе, используя std::tuple
и список типов индексации:
#include <tuple>
#include <iostream>
template<typename... Ts> struct typelist {
template<typename T> using prepend = typelist<T, Ts...>;
};
template<typename T, typename... Ts> struct index;
template<typename T, typename... Ts> struct index<T, T, Ts...>:
std::integral_constant<int, 0> {};
template<typename T, typename U, typename... Ts> struct index<T, U, Ts...>:
std::integral_constant<int, index<T, Ts...>::value + 1> {};
template<int n, typename... Ts> struct nth_impl;
template<typename T, typename... Ts> struct nth_impl<0, T, Ts...> {
using type = T; };
template<int n, typename T, typename... Ts> struct nth_impl<n, T, Ts...> {
using type = typename nth_impl<n - 1, Ts...>::type; };
template<int n, typename... Ts> using nth = typename nth_impl<n, Ts...>::type;
template<int n, int m, typename... Ts> struct extract_impl;
template<int n, int m, typename T, typename... Ts>
struct extract_impl<n, m, T, Ts...>: extract_impl<n, m - 1, Ts...> {};
template<int n, typename T, typename... Ts>
struct extract_impl<n, 0, T, Ts...> { using types = typename
extract_impl<n, n - 1, Ts...>::types::template prepend<T>; };
template<int n, int m> struct extract_impl<n, m> {
using types = typelist<>; };
template<int n, int m, typename... Ts> using extract = typename
extract_impl<n, m, Ts...>::types;
template<typename S, typename T> struct tt_impl;
template<typename... Ss, typename... Ts>
struct tt_impl<typelist<Ss...>, typelist<Ts...>>:
public std::tuple<Ts...> {
template<typename... Args> tt_impl(Args &&...args):
std::tuple<Ts...>(std::forward<Args>(args)...) {}
template<typename S> nth<index<S, Ss...>::value, Ts...> get() {
return std::get<index<S, Ss...>::value>(*this); }
};
template<typename... Ts> struct tagged_tuple:
tt_impl<extract<2, 0, Ts...>, extract<2, 1, Ts...>> {
template<typename... Args> tagged_tuple(Args &&...args):
tt_impl<extract<2, 0, Ts...>, extract<2, 1, Ts...>>(
std::forward<Args>(args)...) {}
};
struct name {};
struct age {};
struct email {};
tagged_tuple<name, std::string, age, int, email, std::string> get_record() {
return { "Bob", 32, "[email protected]"};
}
int main() {
std::cout << "Age: " << get_record().get<age>() << std::endl;
}
Вы, вероятно, захотите написать const
и значение get
средства доступа поверх существующего.
C ++ не имеет struct
тип, который может быть повторяемым как tuple
; это либо / или.
Самое близкое, что вы можете получить к этому через Boost.Fusion’s структурированный адаптер. Это позволяет использовать структуру в качестве последовательности Fusion. Конечно, это также использует серию макросов, и это требует от вас явного перечисления членов структуры в том порядке, в котором вы хотите их перебирать. В заголовке (при условии, что вы хотите перебрать структуру во многих единицах перевода).
На самом деле мой пример, возможно, немного нереалистичен для реализации. Как насчет этого?
Вы могли бы реализовать что-то подобное, но эти идентификаторы должны фактически быть типами, переменными или чем-то еще.
У меня есть своя собственная реализация, которая позволяет вам не объявлять атрибуты в верхней части файла. Также существует версия с объявленными атрибутами, но нет необходимости определять их, достаточно объявления.
Это чистый STL, конечно, и не использовать препроцессор.
Пример:
#include <named_tuples/tuple.hpp>
#include <string>
#include <iostream>
#include <vector>
namespace {
unsigned constexpr operator "" _h(const char* c,size_t) { return named_tuples::const_hash(c); }
template <unsigned Id> using at = named_tuples::attribute_init_int_placeholder<Id>;
using named_tuples::make_tuple;
}
int main() {
auto test = make_tuple(
at<"nom"_h>() = std::string("Roger")
, at<"age"_h>() = 47
, at<"taille"_h>() = 1.92
, at<"liste"_h>() = std::vector<int>({1,2,3})
);
std::cout
<< test.at<"nom"_h>() << "\n"<< test.at<"age"_h>() << "\n"<< test.at<"taille"_h>() << "\n"<< test.at<"liste"_h>().size() << std::endl;
test.at<"nom"_h>() = "Marcel";
++test.get<1>();
std::cout
<< test.get<0>() << "\n"<< test.get<1>() << "\n"<< test.get<2>() << "\n"<< test.get<3>().size() << std::endl;
return 0;
}
Найти полный источник здесь https://github.com/duckie/named_tuple. Не стесняйтесь читать, это довольно просто.
Я реализовал «c ++ named tuple», используя препроцессор boost. Пожалуйста, смотрите пример использования ниже. Получая из кортежа, я получаю сравнение, печать, хэш, сериализация бесплатно (при условии, что они определены для кортежа).
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/comma_if.hpp>#define CM_NAMED_TUPLE_ELEMS_ITR(r, xxx, index, x ) BOOST_PP_COMMA_IF(index) BOOST_PP_TUPLE_ELEM(2,0,x)
#define CM_NAMED_TUPLE_ELEMS(seq) BOOST_PP_SEQ_FOR_EACH_I(CM_NAMED_TUPLE_ELEMS_ITR, "dum", seq)
#define CM_NAMED_TUPLE_PROPS_ITR(r, xxx, index, x) \
BOOST_PP_TUPLE_ELEM(2,0,x) BOOST_PP_CAT(get_, BOOST_PP_TUPLE_ELEM(2,1,x))() const { return get<index>(*this); } \
void BOOST_PP_CAT(set_, BOOST_PP_TUPLE_ELEM(2,1,x))(const BOOST_PP_TUPLE_ELEM(2,0,x)& oo) { get<index>(*this) = oo; }
#define CM_NAMED_TUPLE_PROPS(seq) BOOST_PP_SEQ_FOR_EACH_I(CM_NAMED_TUPLE_PROPS_ITR, "dum", seq)
#define cm_named_tuple(Cls, seq) struct Cls : tuple< CM_NAMED_TUPLE_ELEMS(seq)> { \
typedef tuple<CM_NAMED_TUPLE_ELEMS(seq)> Base; \
Cls() {} \
template<class...Args> Cls(Args && ... args) : Base(args...) {} \
struct hash : std::hash<CM_NAMED_TUPLE_ELEMS(seq)> {}; \
CM_NAMED_TUPLE_PROPS(seq) \
template<class Archive> void serialize(Archive & ar, arg const unsigned int version)() { \
ar & boost::serialization::base_object<Base>(*this); \
} \
}
//
// Example:
//
// class Sample {
// public:
// void do_tata() {
// for (auto& dd : bar2_) {
// cout << dd.get_from() << " " << dd.get_to() << dd.get_tata() << "\n";
// dd.set_tata(dd.get_tata() * 5);
// }
// cout << bar1_ << bar2_ << "\n";
// }
//
// cm_named_tuple(Foo, ((int, from))((int, to))((double, tata))); // Foo == tuple<int,int,double> with named get/set functions
//
// unordered_set<Foo, Foo::hash> bar1_;
// vector<Foo> bar2_;
// };
Обратите внимание, что в приведенном выше примере кода предполагается, что вы определили «общие» функции печати ostream для вектора / tuple / unordered_set.
Реальные проблемы, которые вы должны решить здесь:
ecatmur предложил хорошее решение; но теги не инкапсулированы, и объявление тега несколько неуклюже. C ++ 14 представит адресация кортежей по типу, что упростит его конструкцию и гарантирует уникальность меток, но не решит их объем.
Boost Fusion Map также может использоваться для чего-то подобного, но опять же объявление тегов не является идеальным.
Есть предложение о чем-то похожем на Форум по стандартному предложению c ++, что упростило бы синтаксис, связав имя с параметром шаблона напрямую.
Эта ссылка перечисляет различные способы реализации этого (в том числе ecatmurрешение) и представляет другой вариант использования для этого синтаксиса.
Я «решил» подобную проблему в рабочем коде. Во-первых, у меня есть обычная структура (на самом деле это класс с различными функциями-членами, но нас интересуют только члены-данные) …
class Record
{
std::string name;
int age;
std::string email;
MYLIB_ENABLE_TUPLE(Record) // macro
};
Затем чуть ниже определения структуры, но за пределами любого пространства имен у меня есть другой макрос:
MYLIB_DECLARE_TUPLE(Record, (o.name, o.age, o.email))
Недостаток этого подхода заключается в том, что имена членов должны указываться дважды, но это лучшее, что я смог придумать, в то же время разрешая традиционный синтаксис доступа к элементам в собственных функциях-членах структуры. Макрос появляется очень близко к определениям самих элементов данных, поэтому их не сложно синхронизировать друг с другом.
В другом заголовочном файле у меня есть шаблон класса:
template <class T>
class TupleConverter;
Первый макрос определен так, чтобы объявить этот шаблон friend
структуры, так что он может получить доступ к своим частным членам данных:
#define MYLIB_ENABLE_TUPLE(TYPE) friend class TupleConverter<TYPE>;
Второй макрос определен так, чтобы ввести специализацию шаблона:
#define MYLIB_DECLARE_TUPLE(TYPE, MEMBERS) \
template <> \
class TupleConverter<TYPE> \
{ \
friend class TYPE; \
static auto toTuple(TYPE& o) \
-> decltype(std::tie MEMBERS) \
{ \
return std::tie MEMBERS; \
} \
public: \
static auto toTuple(TYPE const& o) \
-> decltype(std::tie MEMBERS) \
{ \
return std::tie MEMBERS; \
} \
};
Это создает две перегрузки одного и того же имени функции-члена, TupleConverter<Record>::toTuple(Record const&)
который является публичным, и TupleConverter<Record>::toTuple(Record&)
который является частным и доступен только для Record
сам через дружбу. Оба возвращают свои аргументы, преобразованные в кортеж ссылок на личные элементы данных посредством std::tie
, Публичная перегрузка const возвращает кортеж ссылок на const, приватная неконстантная перегрузка возвращает кортеж ссылок на non-const.
После замены препроцессора оба friend
объявления ссылаются на объекты, определенные в том же заголовочном файле, поэтому не должно быть никаких шансов, что другой код нарушит дружбу, чтобы нарушить инкапсуляцию.
toTuple
не может быть функцией-членом Record
потому что его тип возврата не может быть выведен до определения Record
завершено.
Типичное использование выглядит так:
// lexicographical comparison
bool operator< (Record const& a, Record const& b)
{
return TupleConverter<Record>::toTuple(a) < TupleConverter<Record>::toTuple(b);
}
// serialization
std::ostream& operator<< (std::ostream& os, Record const& r)
{
// requires template<class... Ts> ostream& operator<<(ostream&, tuple<Ts...>) defined elsewhere
return os << TupleConverter<Record>::toTuple(r);
}
Это можно расширить многими способами, например, добавив еще одну функцию-член в TupleConverter
который возвращает std::vector<std::string>
из имен членов данных.
Если бы мне было позволено использовать макросы с переменным числом аргументов, решение могло бы быть даже лучше.
Вот еще один способ сделать это, немного сложнее определить типы, но это помогает предотвратить ошибки во время компиляции, потому что вы определяете пары с помощью type_pair
класс (очень похоже std::map
). Следующим шагом будет добавление проверки, чтобы убедиться, что ваши ключи / имя уникальны во время компиляции.
Использование:
using user_t = tagged_tuple<type_pair<struct name, std::string>, type_pair<struct age, int>>;
// it's initialized the same way as a tuple created with the value types of the type pairs (so tuple<string, int> in this case)
user_t user { "chris", 21 };
std::cout << "Name: " << get<name>(user) << std::endl;
std::cout << "Age: " << get<age>(user) << std::endl;
// you can still access properties via numeric indexes as if the class was defined as tuple<string, int>
std::cout << "user[0] = " << get<0>(user) << std::endl;
Я решил не использовать функцию-член, чтобы она была как можно более похожа на std :: tuple, но вы могли бы легко добавить ее в класс.
Исходный код здесь
Вот реализация, аналогичная ответу ecatmur с использованием библиотеки метапрограммирования разбойников (https://github.com/edouarda/brigand):
#include <iostream>
#include <brigand/brigand.hpp>
template<typename Members>
class TaggedTuple{
template<typename Type>
struct createMember{
using type = typename Type::second_type;
};
using DataTuple = brigand::transform<Members, createMember<brigand::_1>>;
using Keys = brigand::keys_as_sequence<Members, brigand::list>;
brigand::as_tuple<DataTuple> members;
public:
template<typename TagType>
auto& get(){
using index = brigand::index_of<Keys, TagType>;
return std::get<index::value>(members);
}
};
int main(){
struct FloatTag{};
struct IntTag{};
struct DoubleTag{};
TaggedTuple<brigand::map<
brigand::pair<FloatTag, float>,
brigand::pair<IntTag, int>,
brigand::pair<DoubleTag, double>>> tagged;
tagged.get<DoubleTag>() = 200;
auto val = tagged.get<DoubleTag>();
std::cout << val << std::endl;
return 0;
}