Подтвердить что ты не робот

Вывод итератора value_type

STL обычно определяет выходной итератор так:

template<class Cont>
class insert_iterator
: public iterator<output_iterator_tag,void,void,void,void> {
    // ...

Почему выходные итераторы определяют value_type как void?. Было бы полезно, чтобы алгоритм знал, какой тип значения он должен выводить.

Например, функция, которая преобразует URL-запрос "key1=value1&key2=value2&key3=value3" в любой контейнер, содержащий элементы строки значения ключа.

template<typename Ch,typename Tr,typename Out>
void parse(const std::basic_string<Ch,Tr>& str, Out result)
{
    std::basic_string<Ch,Tr> key, value;
    // loop over str, parse into p ...
        *result = typename iterator_traits<Out>::value_type(key, value);
}

справочная страница SGI value_type подсказывает это потому, что невозможно разыменовать выходной итератор. Но это не единственное использование value_type: я мог бы создать экземпляр для того, чтобы назначить его итератору.

Какой альтернативный подход для построения значения для вывода с выходным итератором? Два подхода, которые я рассматривал:

  • Примите параметр functor, который вернет объект правильного типа. Я все еще хочу иметь версию алгоритма, который не принимает этот параметр объекта функции.
  • Требовать, чтобы выходной контейнер содержал pair<string,string>, или же тип, конвертируемый из этого. Интересно, могу ли я обойтись без этого требования, возможно, разрешить любой элемент, который может быть построен из двух std::string s.
4b9b3361

Ответ 1

Реальный тип значения итератора вполне может быть итератором. operator* может просто просто вернуть ссылку на *this, потому что реальная работа выполняется оператором присваивания. Вы вполне можете обнаружить, что *it = x; и it = x; имеют точно такой же эффект с выходными итераторами (я полагаю, что специальные меры могут быть предприняты для предотвращения компиляции последнего).

Таким образом, определение типа реального значения было бы столь же бесполезным. С другой стороны, определение его как void может предотвратить ошибки, например:

 typename Iter::value_type v = *it; //useless with an output iterator if it compiled

Я полагаю, что это просто предел концепции итераторов вывода: они являются объектами, которые "злоупотребляют" оператором перегрузкой, чтобы отображаться как указатель, тогда как в действительности происходит нечто совершенно другое.

Ваша проблема интересна. Если вы хотите поддерживать какой-либо контейнер, то соответствующие итераторы вывода, вероятно, будут std::insert_iterator, std::front_insert_iterator и std::back_insert_iterator. В этом случае вы можете сделать что-то вроде следующего:

#include <iterator>
#include <vector>
#include <string>
#include <map>
#include <iostream>

//Iterator has value_type, use it
template <class T, class IterValue>
struct value_type
{
    typedef IterValue type;
};

//output iterator, use the container value_type
template <class Container>
struct value_type<Container, void>
{
    typedef typename Container::value_type type;
};

template <class T, class Out>
void parse_aux(Out out)
{
    *out = typename value_type<T, typename Out::value_type>::type("a", "b");
}

template <template <class> class Out, class T>
void parse(Out<T> out)
{
    parse_aux<T>(out);
}

//variadic template in C++0x could take care of this and other overloads that might be needed
template <template <class, class> class Out, class T, class U>
void parse(Out<T, U> out)
{
    parse_aux<T>(out);
}

int main()
{
    std::vector<std::pair<std::string, std::string> > vec;
    parse(std::back_inserter(vec));
    std::cout << vec[0].first << ' ' << vec[0].second << '\n';

    std::map<std::string, std::string> map;
    parse(std::inserter(map, map.end()));
    std::cout << map["a"] << '\n';

    //just might also support normal iterators
    std::vector<std::pair<std::string, std::string> > vec2(1);
    parse(vec2.begin());
    std::cout << vec2[0].first << ' ' << vec2[0].second << '\n';
}

Это все равно только дало бы вам это далеко. Я предполагаю, что можно было бы это сделать дальше, поэтому он может также управлять, скажем, std::ostream_iterator<printable_type>, но в какой-то момент он будет настолько сложным, что требуется, чтобы бог расшифровал сообщения об ошибках, если что-то пойдет не так.

Ответ 2

Цель value_type итератора - определить тип, возвращаемый при разыменовании этого итератора. Для итераторов вывода единственное законное использование оператора разыменования - это когда оно используется вместе с оператором присваивания - в форме *output_iterator = value. Тип, возвращаемый при разыменовании выходного итератора, не обязательно имеет прямую связь с типами, которые могут быть сохранены через выходной итератор. Единственное необходимое отношение состоит в том, что существует некоторый способ присвоения последних типов первому типу.

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