Reputation: 76317
The method std::basic_string::substr
has the arguments pos, count
, specifying the logical range of elements in positions [pos, pos + count + 1). Conversely, most of the standard library functions (for example, std::for_each
) have arguments, of some form similar to begin, end
, specifying the range [begin, end)
This seems to be an exception, then, confusing to some (see the questions here, here, here, and here). Why wasn't the usual range convention used here? Note also that std::vector::erase
, a method of another random-access container, does follow the usual convention.
Upvotes: 3
Views: 332
Reputation: 179907
Historical reasons. The Standard Library has multiple origins, one of which is the STL. It brought the begin,end
convention. std::string
predates the merger of the STL into the Standard Library, and there was plenty of existing code that used .substr(pos,length)
.
Upvotes: 6
Reputation: 694
I couldn't say why, I wasn't there, but there is a range constructor for the [begin, end) convention you missed.
template< class InputIt >
basic_string( InputIt first, InputIt last,
const Allocator& alloc = Allocator() );
Upvotes: 1
Reputation: 37616
A simple guess:
The different methods you cite have different behavior, which is probably why some use iterators and other don't.
std::for_each
is generic - The easiest way to have a generic version of a method that works on any containers (and even on raw array) is to use iterators.
std::vector::erase
is part of the SequenceContainer concept, so it must have a "generic" form that can work on any kind of container (you could use pos
and count
for a std::vector
, but what about an std::list
? Or a std::set
?). Having such concept is useful to create generic code:
template <typename C>
void real_remove(C &c, const typename C::value_type &value) {
c.erase(std::remove(c.begin(), c.end(), value), c.end());
}
This only works because std::...::erase
is well-defined for any SequenceContainer.
On the other hand, std::basic_string::substr
is only part of std::basic_string
(unlike erase
which is part of std::vector
, std::list
, ...) and returns a std::basic_string
1 (not an iterator, what would you do with an iterator here?).
There are other "non-generic" (i.e. that are not mandatory by some concepts) methods in std::basic_string
, typically the whole family of find
methods, insert
has an overload with size_type
, append
, and so on.
On a subjective view, I think it is better to have a std::basic_string
that does not behave like other containers because it is not (I am not sure that the standard requires std::basic_string
to be a SequenceContainer
or anything alike).
1 You cannot return iterators here because you want a new std::basic_string
, so you would have a method taking iterators but returning an object... I would find this much more disturbing than having pos
/count
instead of first
/last
.
Upvotes: 5