3

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.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Ami Tavory
  • 74,578
  • 11
  • 141
  • 185
  • 2
    You can get same behaviour by using contrustor. E.g. `std::string substring{text.begin(), text.end()}`. Do not know why `substr` does not have such overload. – Zereges Sep 28 '16 at 06:11
  • @Zereges: Because it's redundant. If you have the two iterators denoting the substring, why pass them to a `std::string::substr()` overload if you can also pass them to `std::string::string()`. – MSalters Jul 04 '17 at 11:18
  • @MSalters There are lot of redundant overloads or even functions in STL. STL is not designed to be minimalistic. – Zereges Jul 04 '17 at 11:40

3 Answers3

6

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).

MSalters
  • 173,980
  • 10
  • 155
  • 350
5

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.

Holt
  • 36,600
  • 7
  • 92
  • 139
1

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() );
Loreto
  • 674
  • 6
  • 20