I have two classes that utilize initialize lists. One is a Vector styled class that hold a list of values.
Vec.h:
template< typename T, int nDimensions = 2 >
class Vec{
private:
std::array< T, nDimensions > elements_;
public:
template <typename... U>
Vec(U... ts) : elements_{ ts... } {}
}
Then when used:
typedef Vec< int, 2 > Vec2i;
Vec2i twoi = { 1,2 };
Vec2i twoi2 = twoi; //I have copy constructor and other methods finished.
This class works all fine and dandy, however when I try to use this class with my Matrix styled class, I cant figure out the correct syntax for its constructor.
template< typename T, int X,int Y>
class Mat {
private:
std::array< Vec<T, X>, Y > elements_;
}
Id like to use it like so:
typedef Mat<int, 3,3> Mat3i;
Mat3i threemat = { {1,2,3},
{4,5,6},
{7,8,9}};
Now, iv tried using initializer lists as the constructor with some success, but I cant figure out the syntax to pass the sub lists along.
Mat(std::initializer_list<Vec<T, X>> values) {
for (auto& t : values) {
//What goes here?
}
}
Iv also tried iteration over the list and assigning them manually, but thats a no go.
Its also worth noting that its important that these classes have consecutive chunks of memory for there lists, and no other variables. Otherwise id be using other types instead of std::array. (For casting and union purposes.)
I'm debating if I need to reinterpret cast each value as a Vec then copy over the values.
As alternative:
Mat(std::initializer_list<Vec<T, X>> values) {
std::copy(values.begin(), values.end(), elements_.begin());
}
I'm an idiot.
Mat(std::initializer_list<Vec<T, X>> values) {
int i = 0;
for (auto& t : values) {
elements_[i++] = t;
}
}
Related
I want to build a generic (math) Vector struct, with template defined size.
Now I want to make it possible to access the values via x, y and z, but only if the Vector size is big enough.
Example Code:
template <unsigned int s, typename T>
struct Vector {
// Vector Data array
T v[s];
// Special vars
T& x = v[0];
T& y = v[1];
T& z = v[2];
Vector(): v{0} {}
// some vector functions
};
Now consider the following:
Vector<2, float> vf2;
// should be possible
vf2.x;
// should be impossible
vf2.z;
Vector<3, float> vf3;
// should both be possible
vf3.x;
vf3.z;
The vf2.z part should throw an error at compile time and, because of the functions, I do not want to duplicate the Vector struct.
Does anyone know how to achieve this?
Thanks in advance.
EDIT: I find a much easier way to solve your problem so here goes...
First of all I need to see something about your class design, it is not a good design, if you want everything to be public use a struct not a class, secondly, if you want to check something you need to write a method for it, so something like:
template <unsigned int s, typename T>
class Vector {
public:
T v[s];
T& x = v[0];
T& y = v[1];
T& z = v[2];
int getSize(){
return s;
};
};
Then in your driver you do something like:
Vector<2, float> vf2;
if(vf2.getSize() > 2){
vf2.x;
vf2.z;
}
else{
vf2.z
cout << "x cannot be called because size is too small.";
}
//do the same for the rest....
Lets say I have a class like so:
template< typename T, int nDimensions = 2 >
class Vec
{
private:
std::array< T, nDimensions > elements_;
}
Then I typedef out a few different types.
typedef Vec< int, 2 > Vec2i;
typedef Vec< int, 3 > Vec3i;
typedef Vec< float, 2 > Vec2f;
typedef Vec< float, 3 > Vec3f;
What would the constructor be if I wanted to convert from one type to another?
Vec2i something(10,20); //10,20
Vec2f somethingElse(something); //10.0f,20.0f
Same goes for different sizes:
Vec3f somethingmore(something); //10.0f,20.0f,0.0f
So far I have:
template<typename F>
Vec(const F& other)
{
for (int i = 0; i < nDimensions; i++)
{
this->elements_[i] = static_cast<F>(other[i]); //I know this is wrong.
}
}
I cant figure out a good way to get the base type of the other class to do the static casts on each element, nor a good way to get the others nDimension size so I can do proper bounds checking.
What would the constructor be if I wanted to convert from one type to another?
The most generic constructor would be:
template <typename T2, int nDimension2>
Vec(Vec<T2, nDimension2> const& copy) { ... }
That will need appropriate logic to make sure that you don't access memory using out of bounds indices.
A less generic constructor would be:
template <typename T2>
Vec(Vec<T2, nDimension> const& copy) { ... }
Here, you can use std::copy to copy the elements.
template <typename T2>
Vec(Vec<T2, nDimension> const& copy) { std::copy(copy.elements_.begin(),
copy.elements_.ennd(),
this->elements_.begin()); }
If I had a class that holds N number of equally sized vectors. How would I go about implementing a standard iterator template that would iterate between 1 and N number of the vectors together. I wrote a small example demonstrating the problem.
#include <bitset>
#include <tuple>
#include <type_traits>
#include <vector>
//Since std::get<>() for types isn't in c++11, I use this meta-function to determine the index
//of a Type in a list of Types, starting from 0. ex: IndexOf<C, A, B, C>::value = 2
template <typename T, typename... Ts>
struct IndexOf;
template <typename T, typename... Ts>
struct IndexOf<T, T, Ts...> : std::integral_constant<std::size_t, 0> {};
template <typename T, typename U, typename... Ts>
struct IndexOf<T, U, Ts...> : std::integral_constant<std::size_t, 1 + IndexOf<T, Ts...>::value> {};
//Used to determine the slot we're interesting in.
using Handle = const std::size_t;
template<typename... Types>
class DataManager
{
static constexpr std::size_t TypeCount = sizeof... (Types);
using Flags = std::bitset<TypeCount>; //BitMask to determine if the handle has a certain piece of data initialized
std::size_t count, capacity;
std::tuple<std::vector<Types>..., std::vector<Flags>> vectors; //Tuple of vectors, holding the types and flags.
public:
DataManager(std::size_t n) : count(0), capacity(n),
vectors(std::make_tuple(std::vector<Types>(n)..., std::vector<Flags>(n)))
{}
template <typename Type, typename... Args>
void add(Handle handle, Args&&... args) { //Initializes the type in the handle slot of the vector
Flags& flags = std::get<TypeCount>(vectors)[handle]; //Flag the bit, notify that handle
flags.set(IndexOf<Type, Types...>::value); //has that piece of data initialized
std::get<IndexOf<Type, Types...>::value>(vectors)[handle] = Type{ args... };
}
template <typename Type>
Type& get(Handle handle) { //Returns the Type in handle slot of the vector
return std::get<IndexOf<Type, Types...>::value>(vectors)[handle];
}
template <typename Type>
bool has(Handle handle) { //Returns true if the Type is initialized, by checking the bitset
Flags& flags = std::get<TypeCount>(vectors)[handle];
return flags.test(IndexOf<Type, Types...>::value);
}
Handle push_back() {
return count++;
}
};
Which I currently use like this to access data:
//Simple Data
struct D0 { int x, y; };
struct D1 { float n, m; };
struct D2 { int x, y, z; };
int main()
{
DataManager<D0, D1, D2> manager(100);
Handle h0 = manager.push_back();
std::cout << manager.has<D0>(h0) << std::endl; //prints false, h0 doesn't have D0 initialized
manager.add<D0>(h0, 75, 20); //initialize D0 for h0
std::cout << manager.has<D0>(h0) << std::endl; //prints ture, h0 is now initialzed
std::cout << manager.get<D0>(h0).x << std::endl; //prints 75
}
How could I add iterator functionality to the DataManager class, that would only iterate over selected data like this?
int main()
{
...
for (D0 d1, D3 d3 : manager) {
... //Iterate over all D0s and D3s between 0 and count
}
//or
for(DataManager<D0>::iterator it = v.begin(); it != v.end(); ++it {
... //Iterate over just D0s between 0 and count - 10
}
}
Write a range view type. A range view has two iterators, a begin and an end, and exposes .begin() and .end(). Returning a range view of iterators lets you do a for(:) loop without copying anything.
Next you'll want an iterator over the selected elements. I can think of two approaches.
First, a zip iterator. A zip iterator has a tuple of iterators, and it advances them in parallel. When you dereference, it returns a std::tie of the dereference of each iterator.
Second option, a generator iterator. A generator iterator has an index, and a function that maps from the index to some type. ++ and == etc just advance/compare the index. * returns the result of the function call. In this case, you'd return a tie from the function.
I typically impelement a generator iterator by starting with an indexing iterator (that stores an index, and * returns a copy of it), then writing a transform iterator (that stores an iterator, forwards == and ++ and the like to it, stores a function f, and on * does f(*it) where it is the stored iterator). A generator iterator is now just a transform_iterator<F(index_iterator)>.
These are both effectively limited to satisfying the axioms of an input iterator, due to the rules around ::reference type and the like. However, an input iterator is more than enough to do a for(:) loop; what more, a for(:) loop doesn't even need a legal iterator, as it is defined in terms of code not iterator semantics.
The generator/transform iterator is sufficient, and both are useful in other contexts, so I'd go with that approach.
You can choose to type-erase the transformation (into a std::function< T(std::size_t) >) if you want.
template<class T>
using any_generator_iterator = transform_iterator< std::function<T(std::size_t)>( indexing_iterator ) >;
Now to iterate over types A B and C we do:
template<class...Ts,
class R=std::tuple<Ts&...>,
class It=any_generator_iterator<R>
>
range_view< It >
iterate_over() {
auto get = [this](std::size_t i)->R {
return std::tie( this->get<Ts>()... );
};
return { {get, 0}, {{}, count} };
}
elsewhere:
for( auto i : foo.iterate_over<A,B,C>() ) {
auto&& a = std::get<0>(i);
auto&& b = std::get<1>(i);
auto&& c = std::get<2>(i);
// code
}
with a pile of library code on top of it.
Most, if not all, of this has been solved in boost.
Suppose I have a bunch of vectors:
vector<int> v1;
vector<double> v2;
vector<int> v3;
all of the same length. Now, for every index i, I would like to be able to treat (v1[i], v2[i], v3[i]) as a tuple, and maybe pass it around. In fact, I want to have a a vector-of-tuples rather than a tuple-of-vectors, using which I can do the above. (In C terms, I might say an array-of-structs rather than a struct-of-arrays). I do not want to effect any data reordering (think: really long vectors), i.e. the new vector is backed by the individual vectors I pass in. Let's .
Now, I want the class I write (call it ToVBackedVoT for lack of a better name) to support any arbitrary choice of vectors to back it (not just 3, not int, double and int, not every just scalars). I want the vector-of-tuples to be mutable, and for no copies to be made on construction/assignments.
If I understand correctly, variadic templates and the new std::tuple type in C++11 are the means for doing this (assuming I don't want untyped void* arrays and such). However, I only barely know them and have never worked with them. Can you help me sketch out how such a class will look like? Or how, given
template <typename ... Ts>
I can express something like "the list of template arguments being the replacement of each typename in the original template arguments with a vector of elements of this type"?
Note: I think I might also want to later be able to adjoin additional vectors to the backing vectors, making an instance of ToVBackedVoT<int, double, int> into, say, an instance of ToVBackedVoT<int, double, int, unsigned int>. So, bear that in mind when answering. This is not critically important though.
One idea is to keep the storage in the "struct of array" style in form of vectors for good performance if only a subset of the fields are used for a particular task. Then, for each kind of task requiring a different set of fields, you can write a lightweight wrapper around some of those vectors, giving you a nice random access iterator interface similar to what std::vector supports.
Concerning the syntax of variadic templates, this is how a wrapper class (without any iterators yet) could look like:
template<class ...Ts> // Element types
class WrapMultiVector
{
// references to vectors in a TUPLE
std::tuple<std::vector<Ts>&...> m_vectors;
public:
// references to vectors in multiple arguments
WrapMultiVector(std::vector<Ts> & ...vectors)
: m_vectors(vectors...) // construct tuple from multiple args.
{}
};
To construct such a templated class, it's often preferred to have a template type deducting helper function available (similar to those make_{pair|tuple|...} functions in std):
template<class ...Ts> // Element types
WrapMultiVector<Ts...> makeWrapper(std::vector<Ts> & ...vectors) {
return WrapMultiVector<Ts...>(vectors...);
}
You already see different types of "unpacking" the type list.
Adding iterators suitable to your application (you requested in particular random access iterators) is not so easy. A start could be forward only iterators, which you might extend to random access iterators.
The following iterator class is capable of being constructed using a tuple of element iterators, being incremented and being dereferenced to obtain a tuple of element references (important for read-write access).
class iterator {
std::tuple<typename std::vector<Ts>::iterator...> m_elemIterators;
public:
iterator(std::tuple<typename std::vector<Ts>::iterator...> elemIterators)
: m_elemIterators(elemIterators)
{}
bool operator==(const iterator &o) const {
return std::get<0>(m_elemIterators) == std::get<0>(o.m_elemIterators);
}
bool operator!=(const iterator &o) const {
return std::get<0>(m_elemIterators) != std::get<0>(o.m_elemIterators);
}
iterator& operator ++() {
tupleIncrement(m_elemIterators);
return *this;
}
iterator operator ++(int) {
iterator old = *this;
tupleIncrement(m_elemIterators);
return old;
}
std::tuple<Ts&...> operator*() {
return getElements(IndexList());
}
private:
template<size_t ...Is>
std::tuple<Ts&...> getElements(index_list<Is...>) {
return std::tie(*std::get<Is>(m_elemIterators)...);
}
};
For demonstration purposes, two different patterns are in this code which "iterate" over a tuple in order to apply some operation or construct a new tuple with some epxression to be called per element. I used both in order to demonstrate alternatives; you can also use the second method only.
tupleIncrement: You can use a helper function which uses meta programming to index a single entry and advance the index by one, then calling a recursive function, until the index is at the end of the tuple (then there is a special case implementation which is triggered using SFINAE). The function is defined outside of the class and not above; here is its code:
template<std::size_t I = 0, typename ...Ts>
inline typename std::enable_if<I == sizeof...(Ts), void>::type
tupleIncrement(std::tuple<Ts...> &tup)
{ }
template<std::size_t I = 0, typename ...Ts>
inline typename std::enable_if<I < sizeof...(Ts), void>::type
tupleIncrement(std::tuple<Ts...> &tup)
{
++std::get<I>(tup);
tupleIncrement<I + 1, Ts...>(tup);
}
This method can't be used to assign a tuple of references in the case of operator* because such a tuple has to be initialized with references immediately, which is not possible with this method. So we need something else for operator*:
getElements: This version uses an index list (https://stackoverflow.com/a/15036110/592323) which gets expanded too and then you can use std::get with the index list to expand full expressions. The IndexList when calling the function instantiates an appropriate index list which is only required for template type deduction in order to get those Is.... The type can be defined in the wrapper class:
// list of indices
typedef decltype(index_range<0, sizeof...(Ts)>()) IndexList;
More complete code with a little example can be found here: http://ideone.com/O3CPTq
Open problems are:
If the vectors have different sizes, the code fails. Better would be to check all "end" iterators for equality; if one iterator is "at end", we're also "at end"; but this would require some logic more than operator== and operator!= unless it's ok to "fake" it in; meaning that operator!= could return false as soon as any operator is unequal.
The solution is not const-correct, e.g. there is no const_iterator.
Appending, inserting etc. is not possible. The wrapper class could add some insert or and / or push_back function in order to make it work similar to std::vector. If your goal is that it's syntactically compatible to a vector of tuples, reimplement all those relevant functions from std::vector.
Not enough tests ;)
An alternative to all the variadic template juggling is to use the boost::zip_iterator for this purpose. For example (untested):
std::vector<int> ia;
std::vector<double> d;
std::vector<int> ib;
std::for_each(
boost::make_zip_iterator(
boost::make_tuple(ia.begin(), d.begin(), ib.begin())
),
boost::make_zip_iterator(
boost::make_tuple(ia.end(), d.end(), ib.end())
),
handle_each()
);
Where your handler, looks like:
struct handle_each :
public std::unary_function<const boost::tuple<const int&, const double&, const int&>&, void>
{
void operator()(const boost::tuple<const int&, const double&, const int&>& t) const
{
// Now you have a tuple of the three values across the vector...
}
};
As you can see, it's pretty trivial to expand this to support an arbitrary set of vectors..
From asker's clarification on how this would be used (code that takes a tuple), I'm going to propose this instead.
//give the i'th element of each vector
template<typename... Ts>
inline tuple<Ts&...> ith(size_t i, vector<Ts>&... vs){
return std::tie(vs[i]...);
}
There's a proposal to allow parameter packs to be saved as members of classes (N3728). Using that, here's some untested and untestable code.
template<typename... Types>
class View{
private:
vector<Types>&... inner;
public:
typedef tuple<Types&...> reference;
View(vector<Types>&... t): inner(t...) {}
//return smallest size
size_t size() const{
//not sure if ... works with initializer lists
return min({inner.size()...});
}
reference operator[](size_t i){
return std::tie(inner[i]...);
}
};
And iteration:
public:
iterator begin(){
return iterator(inner.begin()...);
}
iterator end(){
return iterator(inner.end()...);
}
//for .begin() and .end(), so that ranged-based for can be used
class iterator{
vector<Types>::iterator... ps;
iterator(vector<Types>::iterator... its):ps(its){}
friend View;
public:
//pre:
iterator operator++(){
//not sure if this is allowed.
++ps...;
//use this if not:
// template<typename...Types> void dummy(Types... args){} //global
// dummy(++ps...);
return *this;
}
iterator& operator--();
//post:
iterator operator++(int);
iterator operator--(int);
//dereference:
reference operator*()const{
return std::tie(*ps...);
}
//random access:
iterator operator+(size_t i) const;
iterator operator-(size_t i) const;
//need to be able to check end
bool operator==(iterator other) const{
return std::make_tuple(ps...) == std::make_tuple(other.ps...);
}
bool operator!=(iterator other) const{
return std::make_tuple(ps...) != std::make_tuple(other.ps...);
}
};
You may use something like:
#if 1 // Not available in C++11, so write our own
// class used to be able to use std::get<Is>(tuple)...
template<int... Is>
struct index_sequence { };
// generator of index_sequence<Is>
template<int N, int... Is>
struct make_index_sequence : make_index_sequence<N - 1, N - 1, Is...> { };
template<int... Is>
struct make_index_sequence<0, Is...> : index_sequence<Is...> { };
#endif
// The 'converting' class
// Note that it doesn't check that vector size are equal...
template<typename ...Ts>
class ToVBackedVoT
{
public:
explicit ToVBackedVoT(std::vector<Ts>&... vectors) : data(vectors...) {}
std::tuple<const Ts&...> operator [] (unsigned int index) const
{
return at(index, make_index_sequence<sizeof...(Ts)>());
}
std::tuple<Ts&...> operator [] (unsigned int index)
{
return at(index, make_index_sequence<sizeof...(Ts)>());
}
private:
template <int... Is>
std::tuple<const Ts&...> at(unsigned int index, index_sequence<Is...>) const
{
return std::tie(std::get<Is>(data)[index]...);
}
template <int... Is>
std::tuple<Ts&...> at(unsigned int index, index_sequence<Is...>)
{
return std::tie(std::get<Is>(data)[index]...);
}
private:
std::tuple<std::vector<Ts>&...> data;
};
And to iterate, create an 'IndexIterator' like the one in https://stackoverflow.com/a/20272955/2684539
To adjoin additional vectors, you have to create an other ToVBackedVoT as std::tuple_cat does for std::tuple
Conversion to a std::tuple of vectors (vector::iterators):
#include <iostream>
#include <vector>
// identity
// ========
struct identity
{
template <typename T>
struct apply {
typedef T type;
};
};
// concat_operation
// ================
template <typename Operator, typename ...> struct concat_operation;
template <
typename Operator,
typename ...Types,
typename T>
struct concat_operation<Operator, std::tuple<Types...>, T>
{
private:
typedef typename Operator::template apply<T>::type concat_type;
public:
typedef std::tuple<Types..., concat_type> type;
};
template <
typename Operator,
typename ...Types,
typename T,
typename ...U>
struct concat_operation<Operator, std::tuple<Types...>, T, U...>
{
private:
typedef typename Operator::template apply<T>::type concat_type;
public:
typedef typename concat_operation<
Operator,
std::tuple<Types..., concat_type>,
U...>
::type type;
};
template <
typename Operator,
typename T,
typename ...U>
struct concat_operation<Operator, T, U...>
{
private:
typedef typename Operator::template apply<T>::type concat_type;
public:
typedef typename concat_operation<
Operator,
std::tuple<concat_type>,
U...>
::type type;
};
// ToVectors (ToVBackedVoT)
// =========
template <typename ...T>
struct ToVectors
{
private:
struct to_vector {
template <typename V>
struct apply {
typedef typename std::vector<V> type;
};
};
public:
typedef typename concat_operation<to_vector, T...>::type type;
};
// ToIterators
// ===========
template <typename ...T>
struct ToIterators;
template <typename ...T>
struct ToIterators<std::tuple<T...>>
{
private:
struct to_iterator {
template <typename V>
struct apply {
typedef typename V::iterator type;
};
};
public:
typedef typename concat_operation<to_iterator, T...>::type type;
};
int main() {
typedef ToVectors<int, double, float>::type Vectors;
typedef ToVectors<Vectors, int, char, bool>::type MoreVectors;
typedef ToIterators<Vectors>::type Iterators;
// LOG_TYPE(Vectors);
// std::tuple<
// std::vector<int, std::allocator<int> >,
// std::vector<double, std::allocator<double> >,
// std::vector<float, std::allocator<float> > >
// LOG_TYPE(Iterators);
// std::tuple<
// __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >,
// __gnu_cxx::__normal_iterator<double*, std::vector<double, std::allocator<double> > >,
// __gnu_cxx::__normal_iterator<float*, std::vector<float, std::allocator<float> > > >
}
As an alternative similar to boost::zip_iterator I wrote a zip function with a very simple interface:
vector<int> v1;
vector<double> v2;
vector<int> v3;
auto vec_of_tuples = zip(v1, v2, v3);
For example, iterate over these tuples:
for (auto tuple : zip(v1, v2, v3)) {
int x1; double x2; int x3;
std::tie(x1, x2, x3) = tuple;
//...
}
Here, zip() takes any number of ranges of any type. It returns an adaptor which can be seen as a lazily evaluated range over a tuple of elements originating from the wrapped ranges.
The adaptor is part of my Haskell-style functional library "fn" and implemented using variadic templates.
Currently it doesn't support modification of the original ranges' values via the adaptor because of the design of the library (it's intended to be used with non-mutable ranges like in functional programming).
A brief explanation on how this is done is: zip(...) returns an adaptor object which implements begin() and end(), returning an iterator object. The iterator holds a tuple of iterators to the wrapped ranges. Incrementing the iterator increments all wrapped iterators (which is implemented using an index list and unpacking an incrementing expression into a series of expressions: ++std::get<I>(iterators)...). Dereferencing the iterator will decrement all wrapped iterators and pass it to std::make_tuple (which is also implemented as unpacking the expression *std::get<I>(iterators)...).
P.S. Its implementation is based on a lot of ideas coming from answers to this question.
I have a POD with about 30 members of various types and I will be wanting to store thousands of the PODs in a container, and then sort that container by one of those members.
For example:
struct Person{
int idNumber;
....many other members
}
Thousands of Person objects which I want to sort by idNumber or by any other member I choose to sort by.
I've been researching this for a while today and it seems the most efficient, or at least, simplest, solution to this is not use struct at all, and rather use tuple for which I can pass an index number to a custom comparison functor for use in std::sort. (An example on this page shows one way to implement this type of sort easily, but does so on a single member of a struct which would make templating this not so easy since you must refer to the member by name, rather than by index which the tuple provides.)
My two-part question on this approach is 1) Is it acceptable for a tuple to be fairly large, with dozens of members? and 2) Is there an equally elegant solution for continuing to use struct instead of tuple for this?
You can make a comparator that stores a pointer to member internaly so it knows which member to take for comparison:
struct POD {
int i;
char c;
float f;
long l;
double d;
short s;
};
template<typename C, typename T>
struct Comp {
explicit Comp(T C::* p) : ptr(p) {}
bool operator()(const POD& p1, const POD& p2) const
{
return p1.*ptr < p2.*ptr;
}
private:
T C::* ptr;
};
// helper function to make a comparator easily
template<typename C, typename T>
Comp<C,T> make_comp( T C::* p)
{
return Comp<C,T>(p);
}
int main()
{
std::vector<POD> v;
std::sort(v.begin(), v.end(), make_comp(&POD::i));
std::sort(v.begin(), v.end(), make_comp(&POD::d));
// etc...
}
To further generalize this, make make_comp take a custom comparator, so you can have greater-than and other comparisons.
1) Is it acceptable for a tuple to be fairly large, with dozens of members?
Yes it is acceptable. However it won't be easy to maintain since all you'll have to work with is an index within the tuple, which is very akin to a magic number. The best you could get is reintroduce a name-to-index mapping using an enum which is hardly maintainable either.
2) Is there an equally elegant solution for continuing to use struct instead of tuple for this?
You can easily write a template function to access a specific struct member (to be fair, I didn't put much effort into it, it's more a proof of concept than anything else so that you get an idea how it can be done):
template<typename T, typename R, R T::* M>
R get_member(T& o) {
return o.*M;
}
struct Foo {
int i;
bool j;
float k;
};
int main() {
Foo f = { 3, true, 3.14 };
std::cout << get_member<Foo, float, &Foo::k>(f) << std::endl;
return 0;
}
From there, it's just as easy to write a generic comparator which you can use at your leisure (I'll leave it to you as an exercise). This way you can still refer to your members by name, yet you don't need to write a separate comparator for each member.
You could use a template to extract the sort key:
struct A
{
std::string name;
int a, b;
};
template<class Struct, typename T, T Struct::*Member>
struct compare_member
{
bool operator()(const Struct& lh, const Struct& rh)
{
return lh.*Member < rh.*Member;
}
};
int main()
{
std::vector<A> values;
std::sort(begin(values), end(values), compare_member<A, int, &A::a>());
}
Maybe you want to have a look at boost::multi_index_container which is a very powerful container if you want to index (sort) object by different keys.
Create a class which can use a pointer to a Person member data to use for comparison:
std::sort(container.begin(), container.end(), Compare(&Person::idNumber));
Where Compare is:
template<typename PointerToMemberData>
struct Compare {
Compare(PointerToMemberData pointerToMemberData) :
pointerToMemberData(pointerToMemberData) {
}
template<typename Type
bool operator()(Type lhs, Type rhs) {
return lhs.*pointerToMemberData < rhs.*pointerToMemberData
}
PointerToMemberData pointerToMemberData;
};