Как принять общие итераторы?

Я хочу, чтобы конструктор принимал любой итератор, имеющий черту x и ссылающийся на класс y.

Class(std::iterator<std::random_access_iterator_tag, MyClass*> it);

Но когда я пытаюсь передать такой итератор, компиляция завершается с ошибкой Candidate constructor not viable: no known conversion from 'iterator' (aka '__deque_iterator<value_type, pointer, reference, __map_pointer, difference_type, __block_size>') to 'std::iterator<std::random_access_iterator_tag, MyClass *>'.

Код вставки:

std::deque<MyClass*> collection_with_random_access_iterator{};
Class tmp(collection_with_random_access_iterator.begin());

Что я могу здесь сделать?


person Appleshell    schedule 07.09.2013    source источник
comment
Стоит отметить, что вы получаете ошибку, потому что (по крайней мере, в вашей реализации) deque<T>::iterator не наследуется от iterator<random_access_iterator_tag, T> (Стандарт не требует этого). (Кстати, MyClass** также является своего рода итератором произвольного доступа к MyClass* (и, например, vector<MyClass*>::iterator может быть именно таким), но необработанные указатели не могут быть получены ни из чего.)   -  person gx_    schedule 07.09.2013
comment
Пока мы подошли к этому... std::iterator не является полиморфным, это просто вспомогательный базовый класс, предназначенный для упрощения определений итераторов. Это не должно использоваться таким образом.   -  person sbabbi    schedule 07.09.2013
comment
@sbabbi Верно, спасибо, что указали на это. Наследование от удобной структуры std::iterator, даже публичное, является лишь деталью реализации.   -  person gx_    schedule 07.09.2013
comment
@gx_ Жаль, что стандарт С++ не требует, чтобы iterators стандартных коллекций производились от общего родителя.   -  person Appleshell    schedule 07.09.2013
comment
Указатели @AdamS квалифицируются как итераторы произвольного доступа, а vector<T>::iterator и basic_string<T>::iterator могут быть T *. В любом случае наследование не имеет значения; вы не хотели бы использовать виртуальную функцию для разыменования итератора.   -  person Potatoswatter    schedule 08.09.2013


Ответы (2)


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

Например,

    template< typename iterator >
    Class( iterator it ) {
        init_class( * it, typename std::iterator_traits< iterator >::category() );
    }

    template< typename iterator >
    void init_class( iterator it, std::random_access_iterator_tag ) {
        for ( int i = 0; i != 42; i +=3 ) {
            do_something( it[ i ] );
        }
    }

    void do_something( MyClass * ) { … }
};

Передача неправильного итератора приводит к ошибке внутри функции, которая может быть загадочной для пользователя. Но это то, что обычно происходит в реализации стандартной библиотеки, и именно так изначально использовались итераторы до изобретения SFINAE. Если есть распространенные ошибки пользователей, вы можете специально отловить их и привести пользователей к конкретным ошибкам/комментариям.

Если не нужно выбирать другое поведение, но вы хотите убедиться, что пользователь передал итератор произвольного доступа через MyClass *, используйте пару static_assert с условиями std::is_same в качестве ответа sbabbi. Полученный пользовательский интерфейс лучше, чем чистый SFINAE, потому что в сообщении об ошибке говорится: «Пожалуйста, передайте итератор произвольного доступа», а не «Перегрузка не найдена».

person Potatoswatter    schedule 07.09.2013
comment
Стоит отметить, что диспетчеризация тегов и/или static_assert не могут всегда заменить SFINAE, особенно иногда для конструкторов и взаимодействия с трейтами, такими как is_constructible. См. flamingdangerzone.com/cxx11/2013. /02/11/ - person gx_; 07.09.2013
comment
@gx_ Правильный инструмент для работы. Что-то не так с этим ответом или у вас закончились голоса на сегодня? ;в) - person Potatoswatter; 07.09.2013
comment
Ничего страшного, я просто не догадался проголосовать. Исправлено :) (не похоже, что вам нужно больше представителей, но это правда, что голосование важно) - person gx_; 07.09.2013

Что о:

template<class Iterator>
Class(Iterator it,
  typename std::enable_if<
          std::is_same<
                  typename std::iterator_traits<Iterator>::value_type,
                  MyClass*
          >::value //checks for value_type
          &&
          std::is_base_of<
                  std::random_access_iterator_tag,
                  typename std::iterator_traits<Iterator>::iterator_category
          >::value //checks for iterator category
    >::type * = 0);

ИЗМЕНИТЬ также вам следует подумать о замене первого std::is_same на std::is_convertible и проверить наличие const MyClass*, если вы не собираетесь изменять ввод.

person sbabbi    schedule 07.09.2013
comment
Спасибо, это выглядит как вариант, но также довольно огромен. Я бы предпочел более компактное решение, но я буду иметь это в виду и вернусь, если его не будет. - person Appleshell; 07.09.2013
comment
@AdamS: альтернатива состоит в том, чтобы принять любой тип, возможно, назвав параметр шаблона чем-то вроде RandomIterator, чтобы задокументировать его неформальные требования. Это делает код проще, но вы получите более неприятные сообщения об ошибках, если тип не соответствует требованиям. В качестве альтернативы, подождите, пока концепции будут добавлены в язык, надеюсь, в следующем году. - person Mike Seymour; 07.09.2013
comment
Вы можете сделать enable_if менее подробным: flamingdangerzone.com/cxx11/2012 /06/01/almost-static-if.html (источник: stackoverflow.com/a/14623831) - person gx_; 07.09.2013
comment
@Mike Seymour На самом деле это может быть хорошей парой со static_assert (при условии, что это единственные конструкторы шаблонов в классе). - person sbabbi; 07.09.2013