Реализация map_keys_iterator путем деривации: одна ошибка компилятора - PullRequest
1 голос
/ 05 сентября 2010

В выходные я снова работал с C ++ и заметил, что я не уверен, откуда он.

Следуя советам в этой теме , я решил реализовать map_keys_iterator и map_values_iterator. Я выбрал (я думаю) рекомендованный метод извлечения класса из std::map<K,V>::iterator и реализовал его так:

template <typename K, typename V>
struct map_values_iterator:
public std::map<K,V>::iterator {
  // explicitly call base's constructor
  typedef typename std::map<K,V>::iterator mIterator;
  map_values_iterator (const mIterator& mi) : 
  mIterator(mi) {};

  const V& operator* () const { return (*this)->second; }
};

Пока все хорошо, и работает следующий код (nvm Unicode, я по умолчанию работаю с терминалами с поддержкой i18n):

typedef std::map<double,string> Map;
Map constants;
constants[M_PI] = "π";
constants[(1+sqrt(5))/2] = "φ";
constants[exp(M_PI)-M_PI] = "fake_20";
// ... fill map with more constants!
map_values_iterator<double, std::string> vs(constants.begin());
for (; vs != m.end(); ++vs) { 
    cout<< (vs != m.begin() ? ", " : "")<< *vs;
    }
cout<< endl;

Этот код печатает ожидаемый результат, что-то вроде (потому что элементы карты упорядочены):

..., φ, ..., π, ...., fake_20, ....

Так что, я думаю, map_keys_iterator будет работать аналогичным образом. Я позаботился о том, чтобы тип_значения Карты на самом деле был pair<const K, V>, поэтому версия keys вернет значение.

Тем не менее, громоздко нужно объявлять тип итератора, поэтому я хотел создать вызывающую сторону с классической make_pair -подобной идиомой. И вот тут начинается беда:

template <typename K, typename V>
map_values_iterator<K,V> map_values(const typename std::map<K,V>::iterator &i) {
    return lpp::map_values_iterator<K,V> (i);
}

template <typename K, typename V>
map_values_iterator<K,V> map_values(const typename std::map<K,V>::const_iterator &i) {
    return lpp::map_values_iterator<K,V> (i);
}

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

auto vs= map_values(constants.begin());

Я получаю одну ошибку компилятора STL в форме:

error: no matching function for call to ‘map_values(std::_Rb_tree_iterator<std::pair<const double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >)’

Я предполагаю, что в данном конкретном случае весь _Rb_tree_iterator на самом деле является правильным итератором, для которого map::iterator определен с помощью типа; Я не совсем уверен, однако. Я пытался предоставить больше перегрузок, чтобы увидеть, соответствует ли один из них (удалить ссылку, удалить const, использовать только варианты не-const_iterator и т. Д.), Но пока что ничто не позволяет использовать интересующую меня сигнатуру.

Если я сохраню базовый итератор в переменной перед вызовом функции (как в auto begin= constans.begin(); auto vs= map_values(begin);), я получу точно такую ​​же базовую ошибку, очевидно, отличается только описание несопоставленного вызова (в том смысле, что это "const blah & «).

Моей первой попыткой реализации такого рода итераторов было создание базового класса, который агрегирует map::iterator вместо наследования, и получение двух классов, каждый с адекватным operator*, но эта версия столкнулась с гораздо большим количеством проблем, чем выше и по-прежнему вынуждает меня копировать слишком много интерфейса. Поэтому я попробовал эту опцию для кода-экспедиции.

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

Так что ... любые (хорошо инкапсулированные) указатели приветствуются.

1 Ответ

1 голос
/ 05 сентября 2010

Причина заключается в том, что ваши параметры типа K и V находятся в не выводимом контексте, поэтому шаблон вашей функции никогда не создается даже при разрешении перегрузки.

Посмотри еще раз:

template <typename K, typename V>
map_keys_iterator<K,V> map_keys(const typename std::map<K,V>::iterator &i)

Чтобы это работало, компилятору C ++ пришлось бы каким-то образом пройти от определенного класса iterator к его типу "родительского контейнера" ​​- в данном случае map - чтобы получить K и V. В общем, это невозможно - в конце концов, конкретный iterator может быть typedef для какого-то другого класса, а фактическим типом аргумента в вызове является тот другой класс; компилятор не сможет «отследить» его. Таким образом, согласно стандарту C ++, в этом случае он даже не пытается - и, в более общем случае, в любом случае, когда у вас есть typename SomeType<T>::OtherType, а T - параметр типа.

Что вы можете сделать, это сделать весь тип параметра параметром типа шаблона. Это требует некоторой хитрости для получения K и V, однако.

template <typename Iterator>
map_keys_iterator<
     typename std::iterator_traits<Iterator>::value_type::first_type,
     typename std::iterator_traits<Iterator>::value_type::second_type
> map_keys(Iterator i)

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

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

...