A puzzle for template wizards - c++

I'd like to do the following:
const int someInt;
const std::vector<int> someIntList;
const std::vector<std::vector<int>> someNestedIntList;
Marshall(someInt); // trivial case
Marshall(someIntList); // difficult case
Marshall(someNestedIntList); // difficult case
I tried the following:
template<std::vector<class Element>>
void Marshall(const std::vector<Element>& toBeMarshalled)
{
for (int i=0; i<toBeMarshalled.size(); ++i)
Marshall<Element>(toBeMarshalled[i]);
}
Regrettably, this doesn't compile, and I failed to find the right syntax for it.
Note that there has to be only a single template parameter, otherwise the marshalling of a nested list won't work.
Update: Thanks to FredOverflow's answer, I found what I was looking for. I forgot that all container classes in the standard library have a value_type typedef. This can be used as a workaround for my problem:
template <class Container>
void Marshall(const Container& toBeMarshalled)
{
for (UINT32 i=0; i<toBeMarshalled.size(); ++i)
Marshall<Container::value_type>(toBeMarshalled);
}
It is a bit of a patch, but I think it is good enough.

There are two things wrong with your template:
The template declaration is wrong. You only list the template arguments here, not the function argument types. Also, >> is parsed as shift operator.
std::vector has two template parameters. Although in your daily work you will rarely use the second, it's still there and should be listed, or your template will fail if anyone ever attempts to use it with a std::vector that doesn't use the default allocator.
This should work for all std::vector instances:
template< typename T >
void Marshall(const T& toBeMarshalled)
{
// ...
}
template< typename T, class A >
void Marshall(const std::vector<T,A>& toBeMarshalled)
{
for (typename std::vector<T,A>::size_type i=0; i<toBeMarshalled.size(); ++i)
Marshall(toBeMarshalled[i]);
}

The template declaration is wrong. Do:
template< class Element >
void marshall( std::vector< Element > const& v )
Cheers & hth.,

May I propose SFINAE and some boost metaprogramming?
#include <boost/type_traits.hpp>
#include <boost/utility/enable_if.hpp>
template <class T>
struct has_begin_end
{
template <class U,
typename U::const_iterator (U::*)() const,
typename U::const_iterator (U::*)() const>
struct sfinae { };
template <class U>
static char test(sfinae<U, &U::begin, &U::end>*);
template <class>
static long test(...);
enum { value = (1 == sizeof test<T>(0)) };
typedef boost::integral_constant<bool, value> type;
};
template <class Value>
typename boost::disable_if<has_begin_end<Value>, void>::type
Marshall(const Value& value)
{
std::cout << value << ' ';
}
template <class Container>
typename boost::enable_if<has_begin_end<Container>, void>::type
Marshall(const Container& c)
{
std::for_each(c.begin(), c.end(), Marshall<typename Container::value_type>);
}
int main()
{
const int someInt = 42;
const std::vector<int> someIntList {2, 3, 5, 7};
const std::vector<std::vector<int>> someNestedIntList {{11, 13}, {17, 19}};
Marshall(someInt);
Marshall(someIntList);
Marshall(someNestedIntList);
}

The code you've pasted contains >> at the end of your template declaration. C++ compilers will interpret that not as two closing angle brackets, but as a single right-shift operator.
Try template<std::vector<class Element> >, with a space between the brackets.

Related

C++ wrapper around any collection type using templates

Extremely new to c++ however have a question regarding templates
Suppose I have a simple template class as defined below:
template<typename Collection>
class MySack {
private:
Collection c;
public:
typedef typename Collection::value_type value_type;
void add(const value_type& value) {
c.push_back(value);
}
};
The aim of the class being to accept any type of collection, and allow a user to insert the correct type of value for the specified typename Collection.
The obvious problem is that this is only going to work for types which have a push_back method defined, which means it would work with list however not with set.
I started reading about template specialization to see if that'd be any help, however I don't think this would provide a solution as the type contained within the set would have to be known.
How would this problem be approached in c++?
You can use std::experimental::is_detected and if constexpr to make it work:
template<class C, class V>
using has_push_back_impl = decltype(std::declval<C>().push_back(std::declval<V>()));
template<class C, class V>
constexpr bool has_push_back = std::experimental::is_detected_v<has_push_back_impl, C, V>;
template<typename Collection>
class MySack {
private:
Collection c;
public:
typedef typename Collection::value_type value_type;
void add(const value_type& value) {
if constexpr (has_push_back<Collection, value_type>) {
std::cout << "push_back.\n";
c.push_back(value);
} else {
std::cout << "insert.\n";
c.insert(value);
}
}
};
int main() {
MySack<std::set<int>> f;
f.add(23);
MySack<std::vector<int>> g;
g.add(23);
}
You can switch to insert member function, which has the same syntax for std::vector, std::set, std::list, and other containers:
void add(const value_type& value) {
c.insert(c.end(), value);
}
In C++11, you might also want to create a version for rvalue arguments:
void add(value_type&& value) {
c.insert(c.end(), std::move(value));
}
And, kind-of simulate emplace semantics (not truly in fact):
template <typename... Ts>
void emplace(Ts&&... vs) {
c.insert(c.end(), value_type(std::forward<Ts>(vs)...));
}
...
int main() {
using value_type = std::pair<int, std::string>;
MySack<std::vector<value_type>> v;
v.emplace(1, "first");
MySack<std::set<value_type>> s;
s.emplace(2, "second");
MySack<std::list<value_type>> l;
l.emplace(3, "third");
}
I started reading about template specialization to see if that'd be
any help, however I don't think this would provide a solution as the
type contained within the set would have to be known.
You can partially specialize MySack to work with std::set.
template <class T>
class MySack<std::set<T>> {
//...
};
However, this has the disadvantage that the partial specialization replaces the whole class definition, so you need to define all member variables and functions again.
A more flexible approach is to use policy-based design. Here, you add a template parameter that wraps the container-specific operations. You can provide a default for the most common cases, but users can provide their own policy for other cases.
template <class C, class V = typename C::value_type>
struct ContainerPolicy
{
static void push(C& container, const V& value) {
c.push_back(value);
}
static void pop(C& container) {
c.pop_back();
}
};
template <class C, class P = ContainerPolicy<C>>
class MySack
{
Collection c;
public:
typedef typename Collection::value_type value_type;
void add(const value_type& value) {
P::push(c, value);
}
};
In this case, it is easier to provide a partial template specialization for the default policy, because it contains only the functionality related to the specific container that is used. Other logic can still be captured in the MySack class template without the need for duplicating code.
Now, you can use MySack also with your own or third party containers that do not adhere to the STL style. You simply provide your own policy.
struct MyContainer {
void Add(int value);
//...
};
struct MyPolicy {
static void push(MyContainer& c, int value) {
c.Add(value);
}
};
MySack<MyContainer, MyPolicy> sack;
If you can use at least C++11, I suggest the creation of a template recursive struct
template <std::size_t N>
struct tag : public tag<N-1U>
{ };
template <>
struct tag<0U>
{ };
to manage precedence in case a container can support more than one adding functions.
So you can add, in the private section of your class, the following template helper functions
template <typename D, typename T>
auto addHelper (T && t, tag<2> const &)
-> decltype((void)std::declval<D>().push_back(std::forward<T>(t)))
{ c.push_back(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<1> const &)
-> decltype((void)std::declval<D>().insert(std::forward<T>(t)))
{ c.insert(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<0> const &)
-> decltype((void)std::declval<D>().push_front(std::forward<T>(t)))
{ c.push_front(std::forward<T>(t)); }
Observe that the decltype() part enable they (through SFINAE) only if the corresponding method (push_back(), insert() or push_front()) is enabled.
Now you can write add(), in the public section, as follows
template <typename T>
void add (T && t)
{ addHelper<C>(std::forward<T>(t), tag<2>{}); }
The tag<2> element make so the tag<2> addHelper() method is called, if available (if push_back() is available for type C), otherwise is called the tag<1> method (the insert() one) if available, otherwise the tag<0> method (the push_front() one) is available. Otherwise error.
Also observe the T && t and std::forward<T>(t) part. This way you should select the correct semantic: copy or move.
The following is a full working example
#include <map>
#include <set>
#include <list>
#include <deque>
#include <vector>
#include <iostream>
#include <forward_list>
#include <unordered_map>
#include <unordered_set>
template <std::size_t N>
struct tag : public tag<N-1U>
{ };
template <>
struct tag<0U>
{ };
template <typename C>
class MySack
{
private:
C c;
template <typename D, typename T>
auto addHelper (T && t, tag<2> const &)
-> decltype((void)std::declval<D>().push_back(std::forward<T>(t)))
{ c.push_back(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<1> const &)
-> decltype((void)std::declval<D>().insert(std::forward<T>(t)))
{ c.insert(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<0> const &)
-> decltype((void)std::declval<D>().push_front(std::forward<T>(t)))
{ c.push_front(std::forward<T>(t)); }
public:
template <typename T>
void add (T && t)
{ addHelper<C>(std::forward<T>(t), tag<2>{}); }
};
int main ()
{
MySack<std::vector<int>> ms0;
MySack<std::deque<int>> ms1;
MySack<std::set<int>> ms2;
MySack<std::multiset<int>> ms3;
MySack<std::unordered_set<int>> ms4;
MySack<std::unordered_multiset<int>> ms5;
MySack<std::list<int>> ms6;
MySack<std::forward_list<int>> ms7;
MySack<std::map<int, long>> ms8;
MySack<std::multimap<int, long>> ms9;
MySack<std::unordered_map<int, long>> msA;
MySack<std::unordered_multimap<int, long>> msB;
ms0.add(0);
ms1.add(0);
ms2.add(0);
ms3.add(0);
ms4.add(0);
ms5.add(0);
ms6.add(0);
ms7.add(0);
ms8.add(std::make_pair(0, 0L));
ms9.add(std::make_pair(0, 0L));
msA.add(std::make_pair(0, 0L));
msB.add(std::make_pair(0, 0L));
}

C++ variadic template

I am trying to create a polymorhic container working with variadic templates.
Container is initialized as
container<tag, std::string, int, int, int> m;
I want to use following syntax:
auto& v2 = m.find<2, 3, 4>(255, 0, 0);
Template arguments would specify "columns" and for parameters, I want appropriate type to be expected by compiler.
For one template argument (find<2>(255)) I used:
template < int idx > const typename value_type &
find( const typename std::tuple_element<idx, typename value_type>::type &key) {
const std::size_t row_id = getId<idx>(key);
return data_.at(row_id);
}
That worked perfectly, so I wanted to expand it as follows:
template<int ... idx> const typename value_type &
find(const typename std::tuple_element<idx..., typename value_type>::type &keys...) {
const std::size_t row_id = getId<idx...>(keys);
return data_.at(row_id);
}
What's not working at all. Compilation error C2660 - find: function does not take 3 arguments. Can someone explain me, what am I missing here?
Thanks.
EDIT:
Header of container class is
template<typename ... Arguments> class container
value_typementioned is
typedef std::tuple < Arguments... > value_type;
EDIT2:
T.C.'s answer was indeed useful, though I'm still crawling through my bugs with variadic templates. Currently:
enum tag {/*...*/}
int main() {
container<tag, std::string, int, int, int> m;
}
template<typename ... Arguments> class container {
public:
typedef std::tuple < Arguments... > value_type;
std::vector<value_type> data_;
template <int id> void narrowRange(
std::set<std::size_t> & range,
const typename std::tuple_element<id, typename value_type>::type &key)
{
// all commented out
}
template <int id, int ... idx>
void narrowRange(
std::set<std::size_t> & range,
const typename std::tuple_element<id, typename value_type>::type & key,
const typename std::tuple_element<idx, typename value_type>::type & ... keys) // <-
{
narrowRange<idx...>(range, keys...);
// rest commented out
}
Will invoke internal error in MSVS2013 on the marked line. Any suggestions why would be appreciated.
First, value_type doesn't need typename - I'm fairly sure the grammar actually bans it.
Second, you are expanding idx too early, and also incorrectly attempting to expand keys in the declaration. (That second ... is actually being parsed as a C-style varargs.) You are also not expanding the pack keys in the function body. Assuming that you want find<2, 3, 4>(255, 0, 0) to call getId<2, 3, 4>(255, 0, 0), the correct syntax is
template<int ... idx> const value_type &
find(const typename std::tuple_element<idx, value_type>::type &... keys) {
const std::size_t row_id = getId<idx...>(keys...);
return data_.at(row_id);
}

Is there a way to do this using templated functions in c++

I'm currently writing some code to convert java code to c++ code and consequently ending up with some pretty hairy issues. My question is, is it possible to have an overloaded operator that returns the templated value from the containing class?
Ie: I want to be able to do the following with the following classes.
SmartPointer<ArrayClass<bool>*> boolArray = new ArrayClass<bool>(true, true, false, false);
bool b = boolArray[1];
template <typename T> class SmartPointer
{
T data;
template <typename U>
U operator [](int i) const
{
return ((*T)(*data))[index];
}
}
template ArrayClass<U>
{
// Various constructors...
U operator [](int i) const
{
// Implementation here
}
}
The problem I get (understandably) is:
error C2783: 'U SmartPointer::operator const' : could not deduce template argument for 'U'
The compiler doesn't know what U is and I want to be able to tell it that it's bool - because this is what the ArrayClass will be returning. The SmartPointer might not contain an array, in which case the [] operator wouldn't make sense. However I want to be able to pass it through to the object inside the smart pointer in case it does... ?
I don't know what to do to make this work. Perhaps it's not possible??
ANSWER:
Thanks to everyone for responding. There are 3 solutions provided that are essentially the same, but I've award this to Oktalist as he got in first.
I still have a difficulty with this solution though, as I'm passing pointers into my SmartPointer class to allow me to use forward declared classes. This prevented me from using T::value_type as my return type, but that appears to be the right way to do it. It looks like I'm asking to much of the compiler and it looks like I'll have to revert back to simply dereferencing the smartpointer in order to do the array access!
The traditional C++03 way is to use a typedef, typically named value_type. In C++11 we can improve upon this with auto and decltype. Here is your example modified to use both:
SmartPointerCPP03<ArrayClass<bool>> boolArray = new ArrayClass<bool>(true, true, false, false);
SmartPointerCPP11<ArrayClass<bool>> boolArray = new ArrayClass<bool>(true, true, false, false);
bool b = boolArray[1];
template <typename T> class SmartPointerCPP03
{
T* data;
typename T::value_type operator [](int i) const
{
return (*data)[i];
}
}
template <typename T> class SmartPointerCPP11
{
T* data;
auto operator [](int i) const -> decltype(std::declval<T>()[i])
{
return (*data)[i];
}
}
template <typename T> class SmartPointerCPP14
{
T* data;
auto operator [](int i) const
{
return (*data)[i];
}
}
template <typename U> ArrayClass
{
// Various constructors...
typedef U value_type;
U operator [](int i) const
{
// Implementation here
}
}
I also took the liberty of changing T data to T* data and removing the * from the parameter in the instantiation. By the way, your (T*) cast was wrong, and I removed that too.
To start with, make the SmartPointer accept the non-pointer type:
SmartPointer<ArrayClass<bool> > boolArray = new ArrayClass<bool>(true, true, false, false);
Add a typedef to the ArrayClass:
template <typename U> class ArrayClass
{
typedef U value_type;
...
};
Then write a metafunction to get the type:
template <typename T> struct ValueTypeOf {
typedef typename T::value_type type;
};
Then use this in the SmartPointer:
template <typename T>
class SmartPointer
{
typedef typename ValueTypeOf<T>::type value_type;
T* data;
value_type operator [](int i) const
{
return ((*data))[index];
}
};
By using the ValueTypeOf metafunction, you can specialize it based upon the type, so if your type does not have a value_type member, you can do something different to get at it.
Edit: to specialize for a pointer type example:
struct A {
typedef int value_type;
};
template <typename T>
struct ValueTypeOf
{
typedef typename T::value_type type;
};
template <typename T>
struct ValueTypeOf<T*>
{
typedef typename T::value_type type;
};
int main()
{
ValueTypeOf<A>::type foo = 0; // foo is an int
ValueTypeOf<A*>::type bar = 0; // bar is an int
return 0;
}
It's been a while, but I used to do a lot of this. Something like the following should work:
Define a typedef in ArrayClass called value_type, and typedef U to that. Then use T::value_type as the return type of operator [] in SmartPointer.

Detecting a function in C++ at compile time

Is there a way, presumably using templates, macros or a combination of the two, that I can generically apply a function to different classes of objects but have them respond in different ways if they do not have a specific function?
I specifically want to apply a function which will output the size of the object (i.e. the number of objects in a collection) if the object has that function but will output a simple replacement (such as "N/A") if the object doesn't. I.e.
NO_OF_ELEMENTS( mySTLMap ) -----> [ calls mySTLMap.size() to give ] ------> 10
NO_OF_ELEMENTS( myNoSizeObj ) --> [ applies compile time logic to give ] -> "N/A"
I expect that this might be something similar to a static assertion although I'd clearly want to compile a different code path rather than fail at build stage.
From what I understand, you want to have a generic test to see if a class has a certain member function. This can be accomplished in C++ using SFINAE. In C++11 it's pretty simple, since you can use decltype:
template <typename T>
struct has_size {
private:
template <typename U>
static decltype(std::declval<U>().size(), void(), std::true_type()) test(int);
template <typename>
static std::false_type test(...);
public:
typedef decltype(test<T>(0)) type;
enum { value = type::value };
};
If you use C++03 it is a bit harder due to the lack of decltype, so you have to abuse sizeof instead:
template <typename T>
struct has_size {
private:
struct yes { int x; };
struct no {yes x[4]; };
template <typename U>
static typename boost::enable_if_c<sizeof(static_cast<U*>(0)->size(), void(), int()) == sizeof(int), yes>::type test(int);
template <typename>
static no test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(yes) };
};
Of course this uses Boost.Enable_If, which might be an unwanted (and unnecessary) dependency. However writing enable_if yourself is dead simple:
template<bool Cond, typename T> enable_if;
template<typename T> enable_if<true, T> { typedef T type; };
In both cases the method signature test<U>(int) is only visible, if U has a size method, since otherwise evaluating either the decltype or the sizeof (depending on which version you use) will fail, which will then remove the method from consideration (due to SFINAE. The lengthy expressions std::declval<U>().size(), void(), std::true_type() is an abuse of C++ comma operator, which will return the last expression from the comma-separated list, so this makes sure the type is known as std::true_type for the C++11 variant (and the sizeof evaluates int for the C++03 variant). The void() in the middle is only there to make sure there are no strange overloads of the comma operator interfering with the evaluation.
Of course this will return true if T has a size method which is callable without arguments, but gives no guarantees about the return value. I assume wou probably want to detect only those methods which don't return void. This can be easily accomplished with a slight modification of the test(int) method:
// C++11
template <typename U>
static typename std::enable_if<!is_void<decltype(std::declval<U>().size())>::value, std::true_type>::type test(int);
//C++03
template <typename U>
static typename std::enable_if<boost::enable_if_c<sizeof(static_cast<U*>(0)->size()) != sizeof(void()), yes>::type test(int);
There was a discussion about the abilities of constexpr some times ago. It's time to use it I think :)
It is easy to design a trait with constexpr and decltype:
template <typename T>
constexpr decltype(std::declval<T>().size(), true) has_size(int) { return true; }
template <typename T>
constexpr bool has_size(...) { return false; }
So easy in fact that the trait loses most of its value:
#include <iostream>
#include <vector>
template <typename T>
auto print_size(T const& t) -> decltype(t.size(), void()) {
std::cout << t.size() << "\n";
}
void print_size(...) { std::cout << "N/A\n"; }
int main() {
print_size(std::vector<int>{1, 2, 3});
print_size(1);
}
In action:
3
N/A
This can be done using a technique called SFINAE. In your specific case you could implement that using Boost.Concept Check. You'd have to write your own concept for checking for a size-method. Alternatively you could use an existing concept such as Container, which, among others, requires a size-method.
You can do something like
template< typename T>
int getSize(const T& t)
{
return -1;
}
template< typename T>
int getSize( const std::vector<T>& t)
{
return t.size();
}
template< typename T , typename U>
int getSize( const std::map<T,U>& t)
{
return t.size();
}
//Implement this interface for
//other objects
class ISupportsGetSize
{
public:
virtual int size() const= 0;
};
int getSize( const ISupportsGetSize & t )
{
return t.size();
}
int main()
{
int s = getSize( 4 );
std::vector<int> v;
s = getSize( v );
return 0;
}
basically the most generic implementation is always return -1 or "NA" but for vector and maps it will return the size. As the most general one always matches there is never a build time failure
Here you go. Replace std::cout with the output of your liking.
template <typename T>
class has_size
{
template <typename C> static char test( typeof(&C::size) ) ;
template <typename C> static long test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};
template<bool T>
struct outputter
{
template< typename C >
static void output( const C& object )
{
std::cout << object.size();
}
};
template<>
struct outputter<false>
{
template< typename C >
static void output( const C& )
{
std::cout << "N/A";
}
};
template<typename T>
void NO_OF_ELEMENTS( const T &object )
{
outputter< has_size<T>::value >::output( object );
}
You could try something like:
#include <iostream>
#include <vector>
template<typename T>
struct has_size
{
typedef char one;
typedef struct { char a[2]; } two;
template<typename Sig>
struct select
{
};
template<typename U>
static one check (U*, select<char (&)[((&U::size)!=0)]>* const = 0);
static two check (...);
static bool const value = sizeof (one) == sizeof (check (static_cast<T*> (0)));
};
struct A{ };
int main ( )
{
std::cout << has_size<int>::value << "\n";
std::cout << has_size<A>::value << "\n";
std::cout << has_size<std::vector<int>>::value << "\n";
}
but you have to be careful, this does neither work when size is overloaded, nor when it is a template. When you can use C++11, you can replace the above sizeof trick by decltype magic

c++ templates: problem with member specialization

I am attempting to create a template "AutoClass" that create an arbitrary class with an arbitrary set of members, such as:
AutoClass<int,int,double,double> a;
a.set(1,1);
a.set(0,2);
a.set(3,99.7);
std::cout << "Hello world! " << a.get(0) << " " << a.get(1) << " " << a.get(3) << std::endl;
By now I have an AutoClass with a working "set" member:
class nothing {};
template < typename T1 = nothing, typename T2 = nothing, typename T3 = nothing,
typename T4 = nothing, typename T5 = nothing, typename T6 = nothing>
class AutoClass;
template <>
class AutoClass<nothing, nothing, nothing,
nothing, nothing, nothing>
{
public:
template <typename U> void set(int n,U v){}
};
template < typename T1, typename T2, typename T3,
typename T4, typename T5, typename T6>
class AutoClass: AutoClass<T2,T3,T4,T5,T6>
{
public:
T1 V;
template <typename U> void set(int n,U v)
{
if (n <= 0)
V = v;
else
AutoClass<T2,T3,T4,T5,T6>::set(n-1,v);
}
};
and I started to have problems implementing the corresponding "get". This approach doesn't compile:
template < typename T1, typename T2, typename T3,
typename T4, typename T5, typename T6>
class AutoClass: AutoClass<T2,T3,T4,T5,T6>
{
public:
T1 V;
template <typename U> void set(int n,U v)
{
if (n <= 0)
V = v;
else
AutoClass<T2,T3,T4,T5,T6>::set(n-1,v);
}
template <typename W> W get(int n)
{
if (n <= 0)
return V;
else
return AutoClass<T2,T3,T4,T5,T6>::get(n-1);
}
template <> T1 get(int n)
{
if (n <= 0)
return V;
else
return AutoClass<T2,T3,T4,T5,T6>::get(n-1);
}
};
Besides, it seems I need to implement get for the <nothing, nothing, nothing, nothing, nothing, nothing> specialization. Any Idea on how to solve this?
First of all, I prefer Boost.Fusion to Boost.Tuple as it supports a better mixin of template metaprogramming and runtime algorithms I think.
For example, I'd like to present you a little marvel:
struct Name {}; extern const Name name;
struct GivenName {}; extern const GivenName givenName;
struct Age {}; extern const Age age;
class Person
{
public:
template <class T>
struct value
{
typedef typename boost::fusion::result_of::at_key<data_type const,T>::type type;
};
template <class T>
struct has
{
typedef typename boost::fusion::result_of::has_key<data_type,T>::type type;
};
template <class T>
typename value<T>::type
get(T) { return boost::fusion::at_key<T>(mData); }
template <class T>
Person& set(T, typename value<T>::type v)
{
boost::fusion::at_key<T>(mData) = v; return *this;
};
private:
typedef boost::fusion::map <
std::pair<Name, std::string>,
std::pair<GivenName, std::string>,
std::pair<Age, unsigned short>
> data_type;
data_type mData;
};
It's really fun to use:
Person p;
p.set(name, "Rabbit").set(givenName, "Roger").set(age, 22);
Well, I myself prefer indexing by classes than by indices, because I can convey meaning as well as adding type checking ;)
Might I recommend using the Boost library's extensive (and well-tested and cross-platform) set of template-magicky classes? It sounds like what you're looking for is boost::tuple. Any time you can get away with not writing your own code—especially in a complicated situation with templates—you should use someone else's.
As others mentioned, you probably should be able to get where you want by reusing existing implementations from Boost or elsewhere.
If you would be doing something that can't be done using those or if you're curious:
try to keep the pseudo-variadic templates out of the implementation
use type-lists instead to allow for recursive meta-functions etc.
use pseudo-variadic templates as an interface if needed that forwards
to the implementation
do as much at compile-time as possible, especially checks for indices etc.
A simple approach, utilizing MPL for convenience could look something like this:
template<class Types, size_t N> struct holder
// recursively derive from holder types:
: holder<Types, N-1>
{
typename boost::mpl::at_c<Types,N>::type value;
};
// specialization that terminates the recursive derivation:
template<class Types> struct holder<Types,0> {
typename boost::mpl::at_c<Types,0>::type value;
};
template<class Types>
class AutoClass
// recursively derive from holder types:
: holder<Types, boost::mpl::size<Types>::value-1>
{
enum { n = boost::mpl::size<Types>::value };
public:
template<size_t N, class U> void set(const U& u) {
// index check at compile time:
BOOST_STATIC_ASSERT((N < n));
// cast to responsible holder base:
static_cast<holder<Types,N>*>(this)->value = u;
}
template<size_t N> typename boost::mpl::at_c<Types,N>::type get() const {
// index check at compile time:
BOOST_STATIC_ASSERT((N < n));
// cast to responsible holder base:
return static_cast<const holder<Types,N>*>(this)->value;
}
};
Usage:
typedef boost::mpl::vector<int,std::string> Types;
AutoClass<Types> a;
a.set<0>(42);
assert(a.get<0>() == 42);
a.set<1>("abcde");
assert(a.get<1>() == "abcde");
Keep in mind that this can still be wrapped with pseudo-variadic templates for end-user-convenience.
You need to implement for <nothing, nothing...> because of your base case. Consider:
template <typename W> W get(int n)
{
if (n <= 0)
return V;
else
return AutoClass<T2,T3,T4,T5,T6>::get(n-1);
}
Consider what happens when you call this function on a full AutoClass with an n of 5. It creates an autoclass with 5 members and calls with n = 4....and again until it reaches this point:
template <typename W> W get(int n) // current autoclass is <T6,nothing,nothing...>
{
if (n <= 0)
return V;
else
return AutoClass<T2,T3,T4,T5,T6>::get(n-1); // this is <nothing, nothing...>
}
Sure, the call to this autoclass won't happen but the compiler has to compile that code anyway because you've told it to.
You also need to make an AutoClass<nothing,...>::get because n could be 1093.
I don't see a way out of this with your current interface. If you put the n in the template argument you could make a special case that wouldn't do this. In this case you cannot. I think you're going to run into a lot of issues because you've chosen this interface that are going to be rather difficult to solve. For example, what happens when W is 'int' but AutoClass::get(n-1) returns a double or worse, something totally incompatible?