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 请不建议使用元组元素indecies定义枚举。
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;
答案 0 :(得分:43)
我不知道有任何现有的类可以做到这一点,但使用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, "bob@bob.bob"};
}
int main() {
std::cout << "Age: " << get_record().get<age>() << std::endl;
}
您可能希望在现有的访问者之上编写const
和右值get
访问者。
答案 1 :(得分:8)
C ++没有struct
类型,可以像tuple
一样迭代;它是/或。
最接近你的是Boost.Fusion的struct adapter。这允许您将结构用作Fusion序列。当然,这也使用了一系列宏,它要求您按照要迭代它们的顺序显式列出结构的成员。在标题中(假设您想在许多翻译单元中迭代结构)。
实际上我的例子实现起来可能有点不切实际。怎么样?
你可以实现类似的东西,但这些标识符实际上需要是类型或变量或其他东西。
答案 2 :(得分:7)
我有自己的实现来炫耀,它可以让你不要在文件顶部声明属性。也存在具有声明属性的版本,但不需要定义它们,声明就足够了。
当然,它是纯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。随意阅读,很简单。
答案 3 :(得分:1)
我使用boost预处理器实现了“c ++ named tuple”。请参阅下面的示例用法。通过从元组派生,我可以免费获得比较,打印,散列,序列化(假设它们是为元组定义的)。
#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_;
// };
请注意,上面的代码示例假设您已为vector / tuple / unordered_set定义了“通用”ostream打印函数。
答案 4 :(得分:1)
你必须解决的真正问题是:
ecatmur提出了一个很好的解决方案;但标签没有封装,标签声明有点笨拙。 C ++ 14将引入tuple addressing by type,这将简化他的设计并保证标签的唯一性,但不能解决它们的范围。
Boost Fusion Map也可以用于类似的东西,但同样,声明标签并不理想。
c++ Standard Proposal forum上有类似内容的建议,可以通过直接将名称与模板参数相关联来简化语法。
答案 5 :(得分:1)
我已经解决了#34;生产代码中的类似问题。首先,我有一个普通的结构(实际上是一个具有各种成员函数的类,但它只是我们感兴趣的数据成员)...
class Record
{
std::string name;
int age;
std::string email;
MYLIB_ENABLE_TUPLE(Record) // macro
};
然后就在struct定义的下面,但在任何命名空间之外,我有另一个宏:
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的引用元组,私有非const重载返回一个对非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>
个数据成员的名称。
如果我被允许使用可变参数宏,那么解决方案可能会更好。
答案 6 :(得分:1)
以下是另一种方法,定义类型有点麻烦,但它有助于防止编译时出错,因为你用type_pair
类来定义对(很像{ {1}})。下一步是添加检查以确保您的密钥/名称在编译时是唯一的
用法:
std::map
我选择不成为成员函数,使其尽可能与std :: tuple类似,但您可以轻松地在类中添加一个。 Source code here
答案 7 :(得分:0)
这是一个类似于ecatmur使用brigand元编程库(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;
}