Design Topics
<code>iterator_range</code> class An iterator_range is an encapsulation of a pair of iterators that delimit a sequence (or, a range). This concept is widely used by sequence manipulating algorithms. Although being so useful, there no direct support for it in the standard library (The closest thing is that some algorithms return a pair of iterators). Instead all STL algorithms have two distinct parameters for beginning and end of a range. This design is natural for implementation of generic algorithms, but it forbids to work with a range as a single value. It is possible to encapsulate a range in std::pair<>, but the std::pair<> is a too generic encapsulation, so it is not best match for a range. For instance, it does not enforce that begin and end iterators are of the same type. Naturally the range concept is heavily used also in this library. During the development of the library, it was discovered, that there is a need for a reasonable encapsulation for it. A core part of the library deals with substring searching algorithms. Any such an algorithm, returns a range delimiting the result of the search. std::pair<> was considered as unsuitable. Therefore the iterator_range was defined. The intention of the iterator_range class is to manage a range as a single value and provide a basic interface for common operations. Its interface is similar to that of container. In addition of begin() and end() accessors, it has member functions for checking if the range is empty, or to determine the size of the range. It has also a set of member typedefs that extract type information from the encapsulated iterators. As such, the interface is compatible with the container traits requirements so it is possible to use this class as a parameter to many algorithms in this library.
Container Traits Container traits provide uniform access to different types of containers. This functionality allows to write generic algorithms which work with several different kinds of containers. For this library it means, that, for instance, many algorithms work with std::string as well as with char[]. The following container types are supported: Standard containers Built-in arrays (like int[]) Null terminated strings (this includes char[],wchar_t[],char*, and wchar_t*) std::pair<iterator,iterator> Container traits support a subset of container concept (Std §23.1). This subset can be described as an input container concept, e.g. a container with an immutable content. Its definition can be found in the header boost/string_algo/container_traits.hpp. In the table C denotes a container and c is an object of C. Container Traits Name Standard container equivalent Description Maeterlinck container_value_type<C>::type C::value_type Type of contained values container_difference_type<C>::type C::difference_type difference type of the container container_iterator<C>::type C::iterator iterator type of the container container_const_iterator<C>::type C::const_iterator const_iterator type of the container container_result_iterator<C>::type result_iterator type of the container. This type maps to C::iterator for mutable container and C::const_iterator for const containers. begin(c) c.begin() Gets the iterator pointing to the start of the container. end(c) c.end() Gets the iterator pointing to the end of the container. size(c) c.size() Gets the size of the container. empty(c) c.empty() Checks if the container is empty.
The container traits are only a temporary part of this library. There is a plan for a separate submission of a container_traits library to Boost. Once it gets accepted, String Algorithm Library will be adopted to use it and the internal implementation will be deprecated.
Sequence Traits Major difference between std::list and std::vector is not in the interfaces they provide, rather in the inner details of the class and the way how it performs various operation. The problem is that it is not possible to infer this difference from the definitions of classes without some special mechanism. However some algorithms can run significantly faster with the knowledge of the properties of a particular container. Sequence traits allows one to specify additional properties of a sequence container (see Std.§32.2). These properties are then used by algorithms to select optimized handling for some operations. The sequence traits are declared in the header boost/string_algo/sequence_traits.hpp. In the table C denotes a container and c is an object of C. Sequence Traits Trait Description sequence_has_native_replace<C>::value Specifies that the sequence has std::string like replace method sequence_has_stable_iterators<C>::value Specifies that the sequence has stable iterators. It means, that operations like insert/erase/replace do not invalidate iterators. sequence_has_const_time_insert<C>::value Specifies that the insert method of the sequence has constant time complexity. sequence_has_const_time_erase<C>::value Specifies that the erase method of the sequence has constant time complexity
Current implementation contains specializations for std::list<T> and std::basic_string<T> from the standard library and SGI's std::rope<T> and std::slist<T>.
Find Algorithms Find algorithms have similar functionality to std::search() algorithm. They provide a different interface which is more suitable for common string operations. Instead of returning just the start of matching subsequence they return a range which is necessary when the length of the matching subsequence is not known beforehand. This feature also allows a partitioning of the input sequence into three parts: a prefix, a substring and a suffix. Another difference is an addition of various searching methods besides find_first, including find_regex. It the library, find algorithms are implemented in terms of Finders. Finders are used also by other facilities (replace,split). For convenience, there are also function wrappers for these finders to simplify find operations. Currently the library contains only naive implementation of find algorithms with complexity O(n * m) where n is the size of the input sequence and m is the size of the search sequence. There are algorithms with complexity O(n), but for smaller sequence a constant overhead is rather big. For small m << n (m magnitued smaller than n) the current implementation provides acceptable efficiency. Even the C++ standard defines the required complexity for search algorithm as O(n * m). It is possible that a future version of library will also contain algorithms with linear complexity as an option
Replace Algorithms The implementation of replace algorithms follows the layered structure of the library. The lower layer implements generic substitution of a range in the input sequence. This layer takes a Finder object and a Formatter object as an input. These two functors define what to replace and what to replace it with. The upper layer functions are just wrapping calls to the lower layer. Finders are shared with the find and split facility. As usual, the implementation of the lower layer is designed to work with a generic sequence while taking an advantage of specific features if possible (by using Sequence traits)
Split Algorithms Split algorithms are a logical extension of find facility. Instead of searching for one match, the whole input is searched. The result of the search is then used to partition the input. It depends on the algorithms which parts are returned as the result of split operations. It can be the matching parts (find_all()) of the parts in between (split()).