typeid для контейнера STL

Я пишу небольшой класс шаблона, который может быть получен как список параметров шаблона или вектор (и, очевидно, тип данных). Мне нужно перегрузить оператор [ ], для этого я хочу использовать перегруженный [ ] вектора и сделать простой поиск (далее, далее, пока не дойдем до нужного индекса) по списку. Итак, я проверяю с помощью typeid, является ли параметр параметром списка и реализуется в соответствии с результатом следующим образом:

const T* operator[](size_t _index)const
{
    if(typeid(ContainerT<T,std::allocator<T> >) == typeid(vector<T>))
    {
        return m_container[_index];
    }
    else
    {
        const_iterator it = m_container.begin();
        for(int i=0;i<_index;++i)
        {
            ++it;
        }
        return *it;
    }
}

Если я не использую [ ] для списка, все в порядке, но как я его использую:

tContainer_t<int, list> list1;
cout<<list1[0]<<endl;

он вообще не компилируется и вот ошибка компиляции:

In file included from main.cpp:6:0:
tContainer.h: In member function ‘const T* tContainer_t<T, ContainerT>::operator[](size_t) const [with T = int, ContainerT = std::list, size_t = unsigned int]’:
main.cpp:68:9:   instantiated from here
tContainer.h:80:29: error: no match for ‘operator[]’ in ‘((const tContainer_t<int, std::list>*)this)->tContainer_t<int, std::list>::m_container[_index]’

Я не понимаю, так как я проверил, что typeid действительно работает (ну, я думаю...), и в любом случае кажется, что компилятор видит, что индекс также будет вызываться для списка.


person Nathaniel Perez    schedule 30.07.2012    source источник


Ответы (2)


Поскольку компилятору необходимо скомпилировать всю функцию, даже если одна ветвь не будет выбрана во время выполнения (из-за проверки), вы не можете использовать этот тип проверки во время выполнения, чтобы предотвратить ошибку компиляции.

Есть способы сделать это, используя вспомогательную функцию, которая может быть специализирована для типа. Однако это может быть сложно и в этом случае не стоит делать это самостоятельно, так как стандартная библиотека уже сделала всю работу за вас.

std::advance (находится в заголовке <iterator>) может использоваться для продвижения итератора до N раз и оптимизирован для итераторов с произвольным доступом (например, возвращаемых std::vector или std::deque), чтобы делать это за постоянное время. В противном случае он возвращается к шагу по одному, используя ++

// Note, changed signature to return const T&, which is more standard for operator[]
const T& operator[](size_t index) const  
{
   const_iterator itr = m_container.begin();
   std::advance(itr, index);
   return *itr;
}

Редактировать:

Предполагая, что вы хотите узнать, как это делается, вы должны создать следующие функции, обычно в отдельном пространстве имен. На данный момент я собираюсь использовать первоначальную цель вопроса и предположить, что вы используете std::list или std::vector.

namespace helper
{
    template <typename T, typename Alloc>
    typename std::vector<T,Alloc>::const_reference 
    index_into(std::vector<T, Alloc> const& container, std::size_t index)
    {
        return container[index];
    }

    template <typename T, typename Alloc>
    typename std::list<T,Alloc>::const_reference 
    index_into(std::list<T, Alloc> const& container, std::size_t index)
    {
        std::list<T, Alloc>::const_iterator itr = container.begin();
        for(std::size_t i = 0; i < index; ++i) 
        {
            ++itr;
        }
        return *itr;
    }
}
// Change your definition here
const T& operator[](size_t index) const  
{
    return helper::index_into(m_container, index);
}

Почему это работает: когда вы компилируете это с помощью std::list или std::vector, он использует перегрузку функций, чтобы определить, какую из двух перегрузок index_into использовать. Следовательно, он компилирует только тот, который является допустимым, и не пытается скомпилировать оба.

Обратите внимание, реализация позволяет использовать только std::vector и std::list. Если вы хотите разрешить любой контейнер, использование std::advance является общим и правильным решением.

К вашему сведению, реализация std::advaance использует аналогичную технику. Вы можете посмотреть на свою реализацию, чтобы увидеть, как это делается.

person Dave S    schedule 30.07.2012
comment
_index является зарезервированным идентификатором из-за начального подчеркивания и поэтому не должен использоваться. - person yuri kilochek; 31.07.2012
comment
@yuri: отредактировано, я скопировал это из исходного примера. - person Dave S; 31.07.2012
comment
@yurikilochek, нет, это не зарезервированный идентификатор, кроме как в глобальном пространстве имен. Как имя параметра это нормально. См. [global.names] в стандарте. - person Jonathan Wakely; 31.07.2012
comment
@JonathanWakely разве имена макросов не считаются идентификаторами в глобальной области? Или понятие масштаба к ним не применимо? - person yuri kilochek; 31.07.2012
comment
@yurikilochek, при чем здесь макросы? если реализации хотят определить макросы, они должны использовать __index или _Index, а не _index — это имя зарезервировано только в глобальном пространстве имен. - person Jonathan Wakely; 31.07.2012
comment
@JonathanWakely, я вижу. Спасибо. - person yuri kilochek; 31.07.2012
comment
Большое спасибо за ваши очень интересные ответы. Очень полезно. Тем не менее, не могли бы вы объяснить мне, как это сделать, как вы предложили, с помощью вспомогательной функции? - person Nathaniel Perez; 31.07.2012

Используйте std::advance из <iterator>:

const T* operator[](size_t index) const
{
    const_iterator it = m_container.begin();
    std::advance(it, index);
    return &*it;
}
person yuri kilochek    schedule 30.07.2012
comment
Это правильный ответ. std::advance() не зависит от типа контейнера и уже специализирован для разных типов итераторов. Не требуется ни RTTI, ни специализация шаблона. - person Jon Purdy; 31.07.2012