Я думаю, что вы ищете typename C::reference
, см. 23.2.1 [container.requirements.general] §4.
Ой, подождите, вышеописанное не работает, если C
уже const
. Но подождите, decltype
на помощь!
template <typename C>
auto get_nth( C&& c, int i ) -> decltype(*c.begin())
{
//.... some tricky code here ...
}
Если вы также хотите поддерживать массивы в стиле C, которые не имеют begin
функции-члена:
#include <iterator>
template <typename C>
auto get_nth( C&& c, int i ) -> decltype(*std::begin(c))
{
//.... some tricky code here ...
}
И реализация на самом деле не , что сложно:
#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;
}
Обратите внимание, что вышеприведенное решение принимает lvalue и rvalues, но оно всегда будет возвращать ссылку lvalue. В зависимости от кода клиента это может быть проблемой производительности. Возьмите следующий пример кода:
std::string s = get_nth(std::vector<std::string> { "hello", "world" }, 0);
Это скопирует результат в s
, даже если переместить его было бы совершенно правильно (и, конечно, быстрее).
Чтобы решить эту проблему, нам нужны две перегрузки, одна для lvalues и одна для 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);
}
Теперь результат будет перемещен в s
. Часть enable_if
необходима, поскольку из-за правил свертывания ссылок C&&
также может связываться с lvalues, и тогда вызов инициализации s
будет неоднозначным.