I am trying to write a template class which may or may not define a particular member function depending on its template parameter type. Further the return type of this member function depends on the return type of of a member of the template paramter (if defined).
Below is a minimal example of my code
#include <iostream>
#include <type_traits>
template <typename T>
struct has_foo_int {
private:
template <typename U>
static decltype(std::declval<U>().foo(0), void(), std::true_type()) test(int);
template <typename>
static std::false_type test(...);
public:
typedef decltype(test<T>(0)) test_type;
enum { value = test_type::value };
};
template <typename T, bool HasFooInt>
struct foo_int_return_type;
template<typename T>
struct foo_int_return_type<T,false> {};
template<typename T>
struct foo_int_return_type<T,true> {
using type = decltype(std::declval<T>().foo(0));
};
template<typename T>
struct mystruct
{
T val;
//auto someMethod(int i) -> decltype(std::declval<T>().foo(0)) // error: request for member ‘foo’ in ‘std::declval<double>()’, which is of non-class type ‘double’
//auto someMethod(int i) -> typename foo_int_return_type<T,has_foo_int<T>::value>::type // error: no type named ‘type’ in ‘struct foo_int_return_type<double, false>’
template<typename R=typename foo_int_return_type<T,has_foo_int<T>::value>::type> R someMethod(int i) // error: no type named ‘type’ in ‘struct foo_int_return_type<double, false>’
{
return val.foo(i);
}
};
struct with_foo_int {
int foo(int i){
return i+1;
}
};
using namespace std;
int main(void)
{
mystruct<with_foo_int> ms1;
cout << ms1.someMethod(41) << endl;
mystruct<double> ms2;
return 0;
}
What I would like to happen is that the code compiles fine and outputs 42 for ms1.someFunc(41). I would also expect that if one accidentally tried to call someFunc on ms2 that it would fail to compile.
Unfortunately each of the alternatives I have tried has failed. The first and second, I think I understand why they wouldn't work.
I read here that SFINAE only works for template functions so I tried giving a dummy template parameter to work out the return type but this too fails in the same way.
I'm clearly not understanding something here, what am I missing? Is it possible to achieve what I'm trying to do?
Thanks.
P.s. I'm using g++ 4.7.3
P.p.s I have also tried std::enable_if but get much the same results as with my foo_int_return_type struct.
Here is a short, tidy and documented way of doing what you are attempting,
with some possible bugs addressed thereafter.
#include <type_traits>
/*
Template `has_mf_foo_accepts_int_returns_int<T>`
has a static boolean public member `value` that == true
if and only if `T` is a class type that has a public
member function or member function overload
`int T::foo(ArgType) [const]` where `ArgType`
is a type to which `int` is implicitly convertible.
*/
template <typename T>
struct has_mf_foo_accepts_int_returns_int {
/* SFINAE success:
We know now here `int *` is convertible to
"pointer to return-type of T::foo(0)"
*/
template<typename A>
static constexpr bool test(
decltype(std::declval<A>().foo(0)) *prt) {
/* Yes, but is the return-type of `T::foo(0)`
actually *the same* as `int`?...
*/
return std::is_same<int *,decltype(prt)>::value;
}
// SFINAE failure :(
template <typename A>
static constexpr bool test(...) {
return false;
}
/* SFINAE probe.
Can we convert `(int *)nullptr to
"pointer to the return type of T::foo(0)"?
*/
static const bool value = test<T>(static_cast<int *>(nullptr));
};
template<typename T>
struct mystruct
{
using has_good_foo = has_mf_foo_accepts_int_returns_int<T>;
T val;
/* SFINAE:
`template<typename R> R someMethod(R)` will be this if and only
if `R` == `int` and `has_good_foo` == true.
*/
template<typename R = int>
typename std::enable_if<
(has_good_foo::value && std::is_same<R,int>::value),R
>::type
someMethod(R i) {
return val.foo(i);
}
/* SFINAE:
`template<typename R> R someMethod(R)` will be this if and only
if `R` != `int` or `has_good_foo` != true.
*/
template<typename R = int>
typename std::enable_if<
!(has_good_foo::value && std::is_same<R,int>::value),R
>::type
someMethod(R i) {
static_assert(has_good_foo::value && std::is_same<R,int>::value,
"mystruct<T> does not implement someMethod(R)");
return i;
}
};
// Testing...
#include <iostream>
struct with_foo_int
{
int foo(int i) {
return i + 1;
}
};
using namespace std;
int main(void)
{
mystruct<with_foo_int> ms1;
cout << ms1.someMethod(41) << endl;
mystruct<double> ms2;
cout << ms2.someMethod(41) << endl; // static_assert failure
return 0;
}
This solution faithfully reproduces a couple of possible loopholes in your
own attempt as posted:-
1) It looks as if you may believe that evaluating std::declval<U>().foo(0) is
a SFINAE way of determining whether U::foo exists and takes a single argument
of type int. It doesn't. It is merely a SFINAE way of determining whether
U::foo(ArgType) exists where ArgType is anything to which 0 is
implicitly convertible. Thus ArgType could be any pointer-or-arithmetic
type, not just int.
2) You may not have considered that std::declval<U>().foo(0) will be satisfied
if either or both of U::foo(ArgType) U::foo(ArgType) const exists. You
may well care whether you call a const or a non-const member function on
U, and you would certainly care which of two member function you call. If
with_foo_int were defined as:
struct with_foo_int
{
int foo(int i) const {
return i + 1;
}
int foo(int i) {
return i + 2;
}
};
then the solution given would call the non-const overload and
ms1.someMethod(41) would == 43.
2) Is easily dealt with. If you wish to ensure that you can only call
T::foo(ArgType) const then add a const qualifier to mystruct::someMethod.
If you don't care or wish only to call T::foo(ArgType) then leave things
as they are.
1) is a little harder to solve, because you must craft a SNIFAE probe for
T::foo that is satisfied only if it has the right signature, and that
signature will either be const qualified or not. Let's assume you want
int T::foo(int) const. In that case, replace template
has_mf_foo_accepts_int_returns_int with:
/* Template `has_mf_foo_arg_int_returns_int<T>
has a static boolean public member `value` that == true
if and only if `T` is a class type that has an un-overloaded
a public member `int T::foo(int) const`.
*/
template< typename T>
struct has_mf_foo_arg_int_returns_int
{
/* SFINAE foo-has-correct-sig :) */
template<typename A>
static std::true_type test(int (A::*)(int) const) {
return std::true_type();
}
/* SFINAE foo-exists :) */
template <typename A>
static decltype(test(&A::foo))
test(decltype(&A::foo),void *) {
/* foo exists. What about sig? */
typedef decltype(test(&A::foo)) return_type;
return return_type();
}
/* SFINAE game over :( */
template<typename A>
static std::false_type test(...) {
return std::false_type();
}
/* This will be either `std::true_type` or `std::false_type` */
typedef decltype(test<T>(0,0)) type;
static const bool value = type::value; /* Which is it? */
};
and in template mystruct replace:
using has_good_foo = has_mf_foo_accepts_int_returns_int<T>;
with:
using has_good_foo = has_mf_foo_arg_int_returns_int<T>;
(Template has_mf_foo_arg_int_returns_int is adapted
from my other answer and
you can read how it works there.)
What you gain in SFINAE-precision from the latter approach comes at
a price. The approach requires you to attempt to take the address of T::foo,
to see if it exists. But C++ will not give you the address of an overloaded
member function, so this approach will fail if T::foo is overloaded.
The code here will compile (or appropriately static_assert) with
GCC >= 4.7.2 clang >= 3.2.
Related
The problem I'm trying to solve is to sort a template parameter pack according to the return value of a constexpr templated function specialized for each of the types I'm sorting.
I have a list of approximately 100 BOOST_STRONG_TYPEDEFs which creates types TYPE_1, TYPE_2, ..., TYPE_N.
BOOST_STRONG_TYPEDEF(TYPE_1, int)
BOOST_STRONG_TYPEDEF(TYPE_2, double)
// et cetera
BOOST_STRONG_TYPEDEF(TYPE_N, uint8_t)
Then I declare a general template constexpr size_t value_of() for which I specialize for each one of my types:
template<> constexpr size_t value_of<TYPE_1>() { return 1; }
template<> constexpr size_t value_of<TYPE_2>() { return 2; }
// et cetera
template<> constexpr size_t value_of<TYPE_N>() { return n; }
Then I have a class declared as follows. I need to sort each of the types in the UnsortedTypes parameter pack according to the result of value_of.
template<typename ...UnsortedTypes>
class MyClass {
typedef boost::mpl::vector<UnsortedTypes...> UnsortedTypeVector;
typedef typename boost::mpl::sort<
UnsortedTypeVector,
boost::mpl::less<
boost::mpl::size_t<value_of<boost::mpl::placeholders::_1>()>,
boost::mpl::size_t<value_of<boost::mpl::placeholders::_2>()>
>
>::type SortedTypes;
// Utility
void print_types() {
__print_types<SortedTypes>();
}
template<typename Type, typename ...Types>
void __print_types() {
std::cout << typeid(Type).name() << "\n";
if constexpr (sizeof...(Types) > 0) __print_types<Types...>();
}
};
When I test it out as follows:
int main(int, char *[]) {
MyClass<TYPE_5, TYPE_3, TYPE_4, TYPE_2, TYPE_1> myclass;
myclass.print_types();
}
I get this huge, pretty much unintelligible error message which seems to consist of errors within the mpl library.
Intuitively, I have a suspicion that this results from an incorrect definition of my sorting predicate. However, I'm not sure how to fix it!
(This is my first time using Boost.MPL and there aren't many examples online, so please be gentle!)
Here's a reduced example that might make it more obvious what's going on:
namespace mpl = boost::mpl;
template <typename T> constexpr size_t value_of() { return sizeof(T); }
template <typename... Ts>
struct X {
using V = mpl::vector<Ts...>;
using sorted = typename mpl::sort<
V,
mpl::less<
mpl::size_t<value_of<mpl::_1>()>,
// ~~~~~~~~~~~~~~~~~~~
mpl::size_t<value_of<mpl::_2>()>
>
>::type;
};
Now, you intended that this delays the invocation of value_of() until _1 is substituted into. But actually what happens is that it's invoked immediately - because that's what you're asking for. In my case, that's whatever sizeof(_1) ends up being. And so, since these are all constants, the full mpl::less<...> is just some integral constant expression - rather than being a lambda expression, like you wanted it to be.
What you need to do is ensure that invocation is delayed by turning your predicate into a metafunction:
template <typename T>
struct value_of_ : mpl::size_t<sizeof(T)> { };
And then you can use:
template <typename... Ts>
struct X {
using V = mpl::vector<Ts...>;
using sorted = typename mpl::sort<
V,
mpl::less<value_of_<mpl::_1>, value_of_<mpl::_2>>
>::type;
};
I've written this code to check if a class type have begin function.
struct foo //a simple type to check
{
int begin(){ return 0;}
};
struct Fallback
{
int begin(){ return 0;}
};
template<typename T>
struct HasfuncBegin : T,Fallback
{
typedef char one;
typedef int two;
template<typename X>
static one check(int (X::*)() = &HasfuncBegin<T>::begin);
template<typename X>
static two check(...);
enum :bool {yes = sizeof(check<T>())==1, no= !yes};
};
int main()
{
std::cout<< HasfuncBegin<foo>::yes;
return 0;
}
Which produces error :
error: call of overloaded 'check()' is ambiguous
enum {yes = sizeof(check<T>())==1, no= !yes};
^
C:\XXX\main.cpp:24:16: note: candidate: static HasfuncBegin<T>::one HasfuncBegin<T>::check(int (X::*)()) [with X = foo; T = foo; HasfuncBegin<T>::one = char]
static one check(int (X::*)() = &HasfuncBegin<T>::begin);
^
C:\XXX\main.cpp:26:16: note: candidate: static HasfuncBegin<T>::two HasfuncBegin<T>::check(...) [with X = foo; T = foo; HasfuncBegin<T>::two = int]
static two check(...);
^
Can anyone please explain why call is ambiguous (even though first check function with signature one check(int (X::*)() = &HasfuncBegin<T>::begin); has default argument to be used) and also how to make my code work?
Edit:
So here is final working code :
struct foo
{
int begin(){ return 0;}
};
struct Fallback
{
int begin(){ return 0;}
};
template<typename T, T ptr> struct dummy{};
template<typename T>
struct HasfuncBegin : T,Fallback
{
typedef char one;
typedef int two;
template<typename X>
static one check(dummy<int (X::*)(),&HasfuncBegin<X>::begin>*);
// even this won't work, so replace above statement with below commented one
// static one check(dummy<decltype(&HasfuncBegin<X>::begin),&HasfuncBegin<X>::begin>*);
template<typename X>
static two check(...);
enum {yes = sizeof(check<T>(0))==1, no= !yes};
};
The reason for the ambiguity is that both (templated) overloads of check() are valid matches for check<T>(). You may think one is more valid than the other but the rules of the language is that they are both equally valid.
A variable argument function (...) is a match for zero or more arguments (i.e. check<T>()). A function with a single argument that has a default value can match check<T>().
Hence the message about ambiguity.
You haven't actually described what you are trying to achieve with this code (particularly the initialisation of the enum), but are somehow expecting we will work out what you are trying to do. The obvious way to get it to compile would be to remove one of the overloads.
But, unless you describe what you are really trying to achieve, nobody can advise you. Reading sites like this does not grant people mindreading powers.
The call is ambiguous because overload selection is based on conversion sequences from the call arguments to the function parameters. The rules are a bit complex to fully explain here, but consider these two examples:
void ex1(int) {} //v1
void ex1(...) {} //v2
void ex2(int = 1) {} //v1
void ex2(...) {} //v2
int main() {
ex1(1);
ex2();
}
The ex1(1) call is well-formed. There is a single argument which has a better implicit conversion sequence to v1 than v2 (exact match vs. ellipsis conversion).
The ex2() call is ill-formed. There are no arguments with which to compare conversion sequences and both overloads can be called with no arguments. This is analogous to your code.
It looks like you're stuck with C++03, so here's a possible solution using this answer:
template<typename T>
struct HasfuncBegin {
typedef char yes[1];
typedef char no [2];
template <typename U, U> struct type_check;
template <typename _1> static yes &chk(type_check<int (T::*)(), &_1::begin > *);
template <typename > static no &chk(...);
static bool const value = sizeof(chk<T>(0)) == sizeof(yes);
};
Live Demo
My example below suggests that implicit conversions from non-template types to template types won't work as seamlessly as those only involving non-template types. Is there a way to make them work nonetheless?
Example:
struct point;
template<unsigned d> struct vec {
vec() { }
// ...
};
template<> struct vec<2> {
vec() { }
vec(const point& p) { /* ... */ } // Conversion constructor
// ...
};
struct point {
operator vec<2>() { return vec<2>(/* ... */); } // Conversion operator
};
template<unsigned d> vec<d> foo(vec<d> a, vec<d> b) {
return vec<d>(/* ... */);
}
template<unsigned d1, unsigned d2>
vec<d1 + d2> bar(vec<d1> a, vec<d2> b) {
return vec<d1 + d2>(/* ... */);
}
int main(int argc, char** argv) {
point p1, p2;
vec<2> v2;
vec<3> v3;
foo(v2, p1);
foo(p2, v2);
foo(p1, p2);
bar(v3, p1);
}
Is there a way to let this code auto-convert from point to vec<2>?
I know I can overload foo and bar to allow for point arguments, delegating to the vec implementation using an explicit conversion. But doing this for all parameter combinations will become tedious, particularly for functions with many such parameters. So I'm not interested in solutions where I have to duplicate code for every parameter combination of every function.
It appears that neither the conversion constructor nor the cast operator are sufficient to achieve this. At least my gcc 4.7.1 reports no matching function call, although it does name the desired function in a notice, stating that ‘point’ is not derived from ‘vec<d>’.
There is no direct way to get the conversion from point to vec<2>, because at the time when the function call foo(v1,p1) is processed, a function foo that expects a vec<2> as second argument does not exist yet. It's just a function template, and in order for this to be instantiated to a foo(const vec<2> &,const vec<2> &), a function call with these exact argument types would have to be given.
In order for the code to work, the compiler would have to guess both how to instantiate the template parameters, and what type the point argument to convert to. This is too much in the general case (although in your particular code it appears simple, because there is no other possible way to interpret the intent of the programmer).
In terms of solving this, the only thing I can think of is to create highly templated conversion functions:
template <typename T>
struct make_vec
{ };
template <unsigned d>
struct make_vec<vec<d>>
{
static constexpr unsigned dim = d;
using type = vec<dim>;
static const type &from(const type &v)
{ return v; }
};
template <>
struct make_vec<point>
{
static constexpr unsigned dim = 2;
using type = vec<dim>;
static type from(const point &p)
{ return type(p); }
};
template <typename T>
typename make_vec<typename std::decay<T>::type>::type make_vec_from(T&& arg)
{ return make_vec<typename std::decay<T>::type>::from(std::forward<T>(arg)); }
And then implement the foo and bar functions as general templates (accepting all kinds of types, not only vec<d>, using make_vec defined above to convert the given types to the right kind of vec<d>):
namespace detail {
/* Your original implementation of foo. */
template<unsigned d> vec<d> foo(vec<d>, vec<d>) {
return vec<d>(/* ... */);
}
}
/* Templated version of foo that calls the conversion functions (which do
nothing if the argument is already a vec<d>), and then calls the
foo() function defined above. */
template <typename T, typename... Ts>
typename make_vec<typename std::decay<T>::type>::type foo(T&& arg, Ts&&... args)
{ return detail::foo(make_vec_from(arg),make_vec_from(args)...); }
In the case of bar you also need a way to calculate the return type, which is vec<d1+d2+d3...>. For this, a sum calculator is required, also templated:
template <typename... Ts>
struct dsum {
static constexpr unsigned value = 0;
};
template <typename T, typename... Ts>
struct dsum<T,Ts...> {
static constexpr unsigned value = make_vec<typename std::decay<T>::type>::dim + dsum<Ts...>::value;
};
Then, the return type of bar() is vec<dsum<T,Ts...>::value>.
A fully working example is here: http://liveworkspace.org/code/nZJYu$11
Not exactly simple, but might be worth it if you really have extremely many different combinations of arguments.
Here is an example case of what I'm trying to do (it is a "test" case just to illustrate the problem) :
#include <iostream>
#include <type_traits>
#include <ratio>
template<int Int, typename Type>
constexpr Type f(const Type x)
{
return Int*x;
}
template<class Ratio, typename Type,
class = typename std::enable_if<Ratio::den != 0>::type>
constexpr Type f(const Type x)
{
return (x*Ratio::num)/Ratio::den;
}
template</*An int OR a type*/ Something, typename Type>
constexpr Type g(const Type x)
{
return f<Something, Type>(x);
}
int main()
{
std::cout<<f<1>(42.)<<std::endl;
std::cout<<f<std::kilo>(42.)<<std::endl;
}
As you can see, there are two versions of the f() function : the first one takes an int as a template parameter, and the second one takes a std::ratio. The problem is the following :
I would like to "wrap" this function through g() which can take an int OR a std::ratio as first template parameter and call the good version of f().
How to do that without writing two g() functions ? In other words, what do I have to write instead of /*An int OR a type*/ ?
Here's how I would do it, but I've changed your interface slightly:
#include <iostream>
#include <type_traits>
#include <ratio>
template <typename Type>
constexpr
Type
f(int Int, Type x)
{
return Int*x;
}
template <std::intmax_t N, std::intmax_t D, typename Type>
constexpr
Type
f(std::ratio<N, D> r, Type x)
{
// Note use of r.num and r.den instead of N and D leads to
// less probability of overflow. For example if N == 8
// and D == 12, then r.num == 2 and r.den == 3 because
// ratio reduces the fraction to lowest terms.
return x*r.num/r.den;
}
template <class T, class U>
constexpr
typename std::remove_reference<U>::type
g(T&& t, U&& u)
{
return f(static_cast<T&&>(t), static_cast<U&&>(u));
}
int main()
{
constexpr auto h = g(1, 42.);
constexpr auto i = g(std::kilo(), 42.);
std::cout<< h << std::endl;
std::cout<< i << std::endl;
}
42
42000
Notes:
I've taken advantage of constexpr to not pass compile-time constants via template parameters (that's what constexpr is for).
g is now just a perfect forwarder. However I was unable to use std::forward because it isn't marked up with constexpr (arguably a defect in C++11). So I dropped down to use static_cast<T&&> instead. Perfect forwarding is a little bit overkill here. But it is a good idiom to be thoroughly familiar with.
How to do that without writing two g() functions ?
You don't. There is no way in C++ to take either a type or a value of some type, except through overloading.
It is not possible to have a template parameter taking both type and non-type values.
Solution 1:
Overloaded functions.
Solution 2:
You can store values in types. Ex:
template<int n>
struct store_int
{
static const int num = n;
static const int den = 1;
};
template<class Ratio, typename Type,
class = typename std::enable_if<Ratio::den != 0>::type>
constexpr Type f(const Type x)
{
return (x*Ratio::num)/Ratio::den;
}
template<typename Something, typename Type>
constexpr Type g(const Type x)
{
return f<Something, Type>(x);
}
But with this solution you will have to specify g<store_int<42> >(...) instead of g<42>(...)
If the function is small, I advise you to use overloading.
I'd like to hide a std::tuple in my class 'Record' and provide an operator[] on it to access elements of the tuple. The naive code that does not compile is this:
#include <tuple>
template <typename... Fields>
class Record {
private:
std::tuple<Fields...> list;
public:
Record() {}
auto operator[](std::size_t n)
-> decltype(std::get<1u>(list)) {
return std::get<n>(list);
}
};
int main() {
Record<int, double> r;
r[0];
return 0;
}
g++ 4.6 says:
x.cc:13:32: error: no matching function for call to ‘get(std::tuple<int, double>&)’
x.cc:13:32: note: candidates are:
/usr/include/c++/4.6/utility:133:5: note: template<unsigned int _Int, class _Tp1, class _Tp2> typename std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(std::pair<_Tp1, _Tp2>&)
/usr/include/c++/4.6/utility:138:5: note: template<unsigned int _Int, class _Tp1, class _Tp2> const typename std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(const std::pair<_Tp1, _Tp2>&)
/usr/include/c++/4.6/tuple:531:5: note: template<unsigned int __i, class ... _Elements> typename std::__add_ref<typename std::tuple_element<__i, std::tuple<_Elements ...> >::type>::type std::get(std::tuple<_Elements ...>&)
/usr/include/c++/4.6/tuple:538:5: note: template<unsigned int __i, class ... _Elements> typename std::__add_c_ref<typename std::tuple_element<__i, std::tuple<_Elements ...> >::type>::type std::get(const std::tuple<_Elements ...>&)
Basically I'd like to call Record::operator[] just like on an array. is this possible?
The argument to get is a compile time constant. You cannot use a
runtime variable for this and you cannot have a single function that
returns the tuple members as your return type is going to be
wrong. What you can do is to abuse non-type argument deduction:
#include <tuple>
template<typename... Args>
struct Foo {
std::tuple<Args...> t;
template<typename T, std::size_t i>
auto operator[](T (&)[i]) -> decltype(std::get<i>(t)) {
return std::get<i>(t);
}
// also a const version
};
int main()
{
Foo<int, double> f;
int b[1];
f[b];
return 0;
}
This is so horrible, that I would never use it and it won't make much sense to users. I would just forward get through a template member.
I'll try to explain why I think why this is really evil: The return type of a function depends only on compile time facts (this changes slightly for virtual member functions). Let's just assume that non-type argument deduction were possible for some cases (the function call arguments are constexpr) or that we could build something that hides it reasonably well, your users wouldn't realize that their return type just changed and implicit conversion would do nasty things to them. Making this explicit safes some of the trouble.
The error message seems to be misleading, as the problem with your code is pretty much clear:
auto operator[](std::size_t n)
-> decltype(std::get<1u>(list)) {
return std::get<n>(list);
}
The template argument n to std::get must be a constant expression, but in your code above n is not a constant expression.
No.
It is not possible to use a parameter bound at runtime (such as a function parameter) to act as template parameter, because such need be bound at compile-time.
But let's imagine for a second that it was:
Record<Apple, Orange> fruitBasket;
Then we would have:
decltype(fruitBasket[0]) equals Apple
decltype(fruitBasket[1]) equals Orange
is there not something here that bothers you ?
In C++, a function signature is defined by the types of its arguments (and optionally the values of its template parameters). The return type is not considered and does not participate (for better or worse) in the overload resolution.
Therefore, the function you are attempting to build simply does not make sense.
Now, you have two alternatives:
require that all arguments inherit or be convertible to a common type, and return that type (which allows you to propose a non-template function)
embrace templates and require your users to provide specifically the index of the type they wish to use
I do not (and cannot) which alternative is preferable in your particular situation, this is a design choice you will have to make.
Finally, I will remark that you may be reasoning at a too low level. Will your users really need to access each field independently ? If they don't, you could provide facilities to apply functions (visitors ?) to each element in turn, for example.
I think Xeo had code which did this.
Here is my attempt which somewhat works. The problem is that [] is not a reference.
template<typename T, std::size_t N = std::tuple_size<T>::value - 1>
struct foo {
static inline auto bar(std::size_t n, const T& list)
-> decltype(((n != N) ? foo<T, N-1>::bar(n, list) : std::get<N>(list))) {
return ((n != N) ? foo<T, N-1>::bar(n, list) : std::get<N>(list));
}
};
template<typename T>
struct foo<T, 0> {
static inline auto bar(std::size_t n, const T& list)
-> decltype(std::get<0>(list)) {
return std::get<0>(list);
}
};
template <typename... Fields>
class Record {
private:
std::tuple<Fields...> list;
public:
Record() {
std::get<0>(list) = 5;
}
inline auto operator[](std::size_t n)
-> decltype(foo<decltype(list)>::bar(n, list)) {
return foo<decltype(list)>::bar(n, list);
}
};
int main() {
Record<int, double> r;
std::cout << r[0];
return 0;
}
As n is a template parameter, it should be known in compile time, but you want to pass it as a parameter in run-time.
Also, gcc 4.5.2 isn't happy due to this fact:
g++ 1.cpp -std=c++0x
1.cpp: In member function 'decltype (get<1u>(((Record<Fields>*)0)->Record<Fields>::list)) Record<Fields>::operator[](size_t)':
1.cpp:14:25: error: 'n' cannot appear in a constant-expression
If you're fine with a compile-time constant and still want to have the nice operator[] syntax, this is an interesting workaround:
#include <tuple>
template<unsigned I>
struct static_index{
static unsigned const value = I;
};
template <typename... Fields>
class Record {
private:
typedef std::tuple<Fields...> tuple_t;
tuple_t list;
public:
Record() {}
template<unsigned I>
auto operator[](static_index<I>)
-> typename std::tuple_element<
I, tuple_t>::type&
{
return std::get<I>(list);
}
};
namespace idx{
const static_index<0> _0 = {};
const static_index<1> _1 = {};
const static_index<2> _2 = {};
const static_index<3> _3 = {};
const static_index<4> _4 = {};
}
int main() {
Record<int, double> r;
r[idx::_0];
return 0;
}
Live example on Ideone. Though I'd personally just advise to do this:
// member template
template<unsigned I>
auto get()
-> typename std::tuple_element<
I, tuple_t>::type&
{
return std::get<I>(list);
}
// free function
template<unsigned I, class... Fields>
auto get(Record<Fields...>& r)
-> decltype(r.template get<I>())
{
return r.template get<I>();
}
Live example on Ideone.