Общий способ получения const-квалификации элементов из контейнера - PullRequest
0 голосов
/ 14 января 2012

Мне нужна универсальная функция, которая может принимать постоянную или неконстантную ссылку на контейнер и возвращать соответствующую ссылку на элементы, определенные в соответствии с контейнером.

Что-то вроде этого:

template <typename C>
auto get_nth( C& c, int i ) -> /* not-sure-what, but let's call it T */
{
      //.... some tricky code here ...
}

Я хотел бы подчеркнуть, что если C расширяется до

SomeContainer const

тогда Т будет

SomeContainer::const_reference

и другие

SomeContainer::reference

Я думаю, что могу собрать это вместе, используя type traits и mtl if, мой вопрос, есть ли более короткий, более чистый способ.

Я использую C ++ x11 (очевидно) и boost.

Заранее спасибо.

1 Ответ

3 голосов
/ 14 января 2012

Я думаю, что вы ищете 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 будет неоднозначным.

...