Попутно при тестировании итератора
iterator_pair я обнаружил серьезный дефект стандарта
C++. Понять этот дефект можно на простом примере. пусть заданы два выходных итератора
std::ostream_iterator<int> it1( std::cout, "\n" );
std::ostream_iterator<std::string> it2( std::cout, "\n" );
Они могут быть использованы со следующими типами объектов
it1 = 123;
it2 = "abc";
Но их нельзя использовать с теми типами объектов, для которых они не предназначены. То есть нельзя написать, например,
it1 = "abc";
it2 = 123;
Компилятор выдаст сообщение об ошибке, что ни первый, ни второй итератор не имеют соответствующих перегруженных операторов присваивания для типов объектов, стоящих в правой части выражения.
Что это означает?
Это означает, что каждый выходной итератор может работать лишь с объектами определенного типа. Тип объекта, с которым может иметь дело выходной итератор, является неотъемлемой и важной характеристикой итератора. И хотя в примере с итераторами, показанном выше, оба итератора являются специализацией одного и того же шаблонного класса, тип значения, с которым они могут иметь дело, у каждого итератора свой.
Другой пример.
Пусть есть некоторая шаблонная функция, которая имеет шаблонный параметр в виде выходного итератора.
template <class OutputIterator>
void f( OutputIterator it )
{
// some code
}
И допустим в теле функции мы хотим объявить переменную, которая будет иметь тип, соответствующий тому типу, с которым может иметь дело выходной итератор. Как объявить такую переменную? Как узнать ее тип?
Увы, выходные итераторы в стандарте
C++ определяются таким образом, что их характеристика
typename std::iterator_traits<OutputIterator>::value_type задается как
void! Это означает, что нет никакой возможности выяснить, с каким типом объектов может работать итератор.
С другой стороны, если мы возьмем какой-нибудь выходной итератор, описанный в стандарте
C++, то мы увидим, что на самом деле выходные итераторы неявно определяют
value_type при своей реализации, только скрывают это от внешнего мира.
Например, вот как определяется в стандарте
C++ выходной итератор
std::ostream_iterator:
template <class T, class charT = char, class traits = char_traits<charT> >
class ostream_iterator:
public iterator<output_iterator_tag, void, void, void, void> {
// пропущены не имеющие для данного вопроса объявления
ostream_iterator<T,charT,traits>& operator=(const T& value);
Из этого объявления итератора видно, что тип значения, с которым имеет дело итератор, это шаблонный параметр
T. Однако для внешнего мира итератор объявляет, что его тип значения (
value_type) - это
void!
class ostream_iterator:
public iterator<output_iterator_tag, void, void, void, void> {
Значит для внешнего мира нет никакой возможности определить, с каким реальным типом объектов имеет дело итератор.
Аналогичная ситуация и с итератором
std::back_insert_iterator:
template <class Container>
class back_insert_iterator :
public iterator<output_iterator_tag,void,void,void,void> {
// ...
back_insert_iterator<Container>&
operator=(const typename Container::value_type& value);
Этот итератор неявно объявляет тип значения как
typename Container::value_type, тем не менее для внешнего мира его тип значения равен
void:
class back_insert_iterator :
public iterator<output_iterator_tag,void,void,void,void> {
Как это отразилось на новом итераторе
iterator_pair, предложенным мною?
Рассмотрим следующий пример использования итератора
iterator_pair
#include <iostream>
#include <iterator>
#include <algorithm>
#include <vector>
#include <string>
#include <sstream>
int main()
{
std::map<int, std::string> m;
std::string s = "Hello new iterator \"std::iterator_pair\"!";
std::istringstream is( s );
int i = 0;
std::transform( std::istream_iterator<std::string>( is ),
std::istream_iterator<std::string>(),
std::inserter( m, m.begin() ),
[&i]( const std::string &s ) { return ( std::make_pair( i++, s ) ); } );
std::vector<int> v1( m.size() );
std::vector<std::string> v2( m.size() );
std::copy( m.begin(), m.end(),
usr::make_iterator_pair( v1.begin(), v2.begin() ) );
for ( int x : v1 ) std::cout << x << ' ';
std::cout << std::endl;
for ( const std::string &s : v2 ) std::cout << s << ' ';
std::cout << std::endl;
}
Этот код успешно компилируется и работает. Итератор
iterator_pair имеет доступ к
value_type итераторов
v1.begin() и
v2.begin(), так как эти итераторы не являются только выходными, а потому предоставляют внешнему миру конкретный тип объектов, с которыми они имеют дело. В данном случае. это типы
int и
std::string.
Но если изменить код следующим образом
#include <iostream>
#include <iterator>
#include <algorithm>
#include <vector>
#include <string>
#include <sstream>
int main()
{
std::map<int, std::string> m;
std::string s = "Hello new iterator \"std::iterator_pair\"!";
std::istringstream is( s );
int i = 0;
std::transform( std::istream_iterator<std::string>( is ),
std::istream_iterator<std::string>(),
std::inserter( m, m.begin() ),
[&i]( const std::string &s ) { return ( std::make_pair( i++, s ) ); } );
std::vector<int> v1;
std::vector<std::string> v2;
v1.reserve( m.size() );
v2.reserve( m.size() );
std::copy( m.begin(), m.end(),
usr::make_iterator_pair( std::back_inserter( v1 ), std::back_inserter( v2 ) ) );
for ( int x : v1 ) std::cout << x << ' ';
std::cout << std::endl;
for ( const std::string &s : v2 ) std::cout << s << ' ';
std::cout << std::endl;
}
то код не будет компилироваться, так как выходной итератор, возвращаемый функцией
std::back_inserter, не сообщает реальный тип
value_type, а вместо него предоставляет тип
void.
В результате этого итератор
iterator_pair не может определить реальный тип значения, с которым он должен работать и передать на вход операторам присваивания обернутых им двух итераторов.
Это серьезный дефект стандарта
C++. Каждый выходной итератор имеет конкретное значения для характеристики
value_type, с которой он может иметь дело, и эту информацию он должен предоставлять внешнему миру.