Ami Tavory
Ami Tavory

Reputation: 76317

Why doesn't std::basic_string::substr follow the [begin, end) convention?

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

Answers (3)

MSalters
MSalters

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

Loreto
Loreto

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

Holt
Holt

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_string1 (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

Related Questions