I think you are looking for typename C::reference, see 23.2.1 [container.requirements.general] §4.
Oh wait, the above doesn't work if C is already const. But wait, decltype to the rescue!
template <typename C>
auto get_nth( C&& c, int i ) -> decltype(*c.begin())
{
//.... some tricky code here ...
}
If you also want to support C-style arrays which have no begin member function:
#include <iterator>
template <typename C>
auto get_nth( C&& c, int i ) -> decltype(*std::begin(c))
{
//.... some tricky code here ...
}
And the implementation really isn't that tricky:
#include <iterator>
template <typename C>
auto get_nth( C&& c, int i ) -> decltype(*std::begin(c))
{
auto it = std::begin(c);
std::advance(it, i);
return *it;
}
Note that the above solution accepts lvalues and rvalues, but it will always return an lvalue reference. Depending on the client code, this may be a performance concern. Take the following example code:
std::string s = get_nth(std::vector<std::string> { "hello", "world" }, 0);
This will copy the result into s, even though moving it would be perfectly valid (and, of course, faster).
To solve this problem, we need two overloads, one for lvalues and one for rvalues:
#include <iterator>
#include <type_traits>
template <typename C>
auto get_nth( C& c, int i ) -> decltype(*std::begin(c))
{
auto it = std::begin(c);
std::advance(it, i);
return *it;
}
template <typename C>
auto get_nth( C&& c, int i )
-> typename std::enable_if<std::is_rvalue_reference<C&&>::value,
decltype(std::move(*std::begin(c)))>::type
{
auto it = std::begin(c);
std::advance(it, i);
return std::move(*it);
}
Now the result will be moved into s. The enable_if part is necessary because due to reference collapsing rules, C&& can also bind to lvalues, and then the call to initialize s would be ambiguous.
get_nth, do you want to return an lvalue reference or an rvalue reference to the i-th element?*std::next(begin(c), i);?