Почему значение value_type/difference_type/указатель/ссылка back_insert_iterator/front_insert_iterator/insert_iterator недействительны?
В моем проекте я хочу разделить поток на некоторый заданный тип значений, поэтому я реализую функцию шаблона как
template <typename TElem, typename TOutputIter>
TOutputIter SplitSpace(std::istream& IS, TOutputIter result)
{
TElem elem;
while (IS >> elem)
{
*result = elem;
++result;
}
return result;
}
Я думаю, что это неудобно, так как я должен явно указывать тип TElem
при вызове. Например, я должен написать:
std::vector<int> v;
SplitSpace<int>(std::cin, back_inserter(v));
// I want to it to be SplitSpace(std::cin, back_inserter(v));
Я попытался получить тип значения из итератора (шаблона) и использовал std::iterator_traits
следующим образом:
template <typename TOutputIter>
TOutputIter SplitSpace(std::istream& IS, TOutputIter result)
{
typename std::iterator_traits<TOutputIter>::value_type elem;
while (IS >> elem)
{
*result = elem;
++result;
}
return result;
}
Однако приведенные выше коды не работают для back_insert_iterator
. Я проверил исходные коды back_insert_iterator/front_insert_iterator/insert_iterator
в пространстве имен std
и нашел, что value_type/difference_type/pointer/reference
- все void
.
Я хотел бы знать, почему все эти типы void
, есть ли какое-либо соображение для этого? Другой вопрос: возможно ли реализовать функцию SplitSpace
без явного указания типа элемента при вызове? Спасибо.
Ответы
Ответ 1
Как упоминалось Люком в комментариях, что вы хотите сделать, легко выполнить со стандартной библиотекой:
std::vector<int> v;
std::copy( std::istream_iterator( std::cin )
, std::istream_iterator()
, std::back_inserter( v ) );
Ответ 2
value_type
не имеет особого смысла в случае OutputIterators, потому что выходной итератор не дает доступа к каким-либо значениям, и, что более важно, он может принимать широкий диапазон типов значений.
Единственным требованием для OutputIterator it
является то, что он должен поддерживать выражение *it = o
, где o - значение некоторого типа, которое находится в наборе типов, которые могут быть записаны для конкретного типа итератора i (§24.2.1). Это означает, что выходной итератор может потенциально принимать широкий диапазон типов: например, его operator*
может возвращать прокси-объект, который перегружает operator=
для различных типов; что должно быть value_type
в этом случае?
Например, рассмотрим следующий выходной итератор:
struct SwallowOutputIterator :
public std::iterator<output_iterator_tag, void, void, void, void>
{
struct proxy // swallows anything
{
template <typename T>
void operator=(const T &) {}
};
proxy operator*()
{
return proxy();
}
// increment operators
};
Для value_type
здесь нет разумного выбора.
Те же рассуждения применимы к pointer
и reference_type
. difference_type
не определяется, потому что вы не можете рассчитать расстояние между двумя итераторами вывода, поскольку они однопроходные.
N.B.: стандарт явно указывает, что insert_iterator
и его братья и сестры должны наследоваться от iterator<output_iterator_tag, void, void, void, void>
, поэтому это не является особенностью вашей реализации.
Ответ 3
Если вы просто ищете способ избежать указания параметра шаблона TElem
, вы можете использовать этот подход:
template <typename TOutputIter>
TOutputIter SplitSpace(std::istream& IS, TOutputIter result)
{
typedef typename TOutputIter::container_type::value_type TElem;
TElem elem;
while (IS >> elem)
{
*result = elem;
++result;
}
return result;
}
... конечно, в зависимости от того, какие типы TOutputIter вы притворяетесь используемыми, вы не должны использовать этот подход.
Ответ 4
AFAIK, вы должны иметь возможность получить container_type
из итератора, из которого вы должны получить value_type
. Вероятно, вы захотите в какой-то момент специализироваться на pair
. Это должно ответить на вторую часть, как и в первой части; не уверен...