Delta_Fore
Delta_Fore

Reputation: 3271

Using make_iterator_range with std::string

I'd like to do a to_upper() call on a substring, and thus pass in a range. I don't understand why I cannot construct a range on a string, but can on a vector in the same way.

Using Xcode 6.3.2 with compiler option -std=c++11 and Boost 1.58

void string_test() {
    std::string my_str = "Hello, world, it's a wonderful day";    
    std::vector<int> x  = {1,2,3,4,5,6,6,8,9,10};
    const auto rng = boost::make_iterator_range(x.begin(),x.end()-5);
    const auto rng2 = boost::make_iterator_range(my_str.begin(),my_str.begin()+10);
    // How do I get to_upper_copy to accept the range returned above
    std::cout << boost::to_upper_copy(rng2) << std::endl;

}

I get an error in Xcode:

/usr/local/Cellar/boost/1.58.0/include/boost/range/iterator_range_core.hpp:215:11: No matching constructor for initialization of 'std::__1::__wrap_iter<char *>'

Upvotes: 2

Views: 2880

Answers (2)

Delta_Fore
Delta_Fore

Reputation: 3271

I finally realised after @Praetorian's answer that you can remove the smaller string allocation or vector copy completely by using the overloaded to_upper_copy() passing in the output iterator.

void string_algo() {
    const std::string my_str = "Hello, world, it's a wonderful day";
    const auto rng2 = boost::make_iterator_range(my_str.begin(),my_str.begin()+10);
    boost::to_upper_copy(rng2.begin(),rng2);
    std::cout << rng2 << std::endl;
}

Upvotes: 0

Praetorian
Praetorian

Reputation: 109119

The error doesn't have anything to do with creating an iterator_range from string; if you comment out the call to to_upper_copy, your code will compile. Similarly, if you swap string with vector<char> (or even vector<int>), the call to to_upper_copy will again fail to compile with similar errors.

Clearly, the culprit is to_upper_copy, and looking at the signature of the function template tells us why.

template<typename SequenceT> 
SequenceT to_upper_copy(const SequenceT &, 
                        const std::locale & = std::locale());

You cannot pass in a pair of string::iterators, which is what your make_iterator_range call essentially returns , as SequenceT because there would be no easy way for the algorithm to construct a new SequenceT given a pair of iterators. This is described in the Boost documentation as well:

In addition some algorithms have additional requirements on the string-type. Particularly, it is required that an algorithm can create a new string of the given type. In this case, it is required that the type satisfies the sequence (Std §23.1.1) requirements.

In the reference and also in the code, requirement on the string type is designated by the name of template argument. RangeT means that the basic range requirements must hold. SequenceT designates extended sequence requirements.

Note that the section referred to above is from the C++03 standard. In C++11 and later documents, the relevant section is §23.2.3 [sequence.reqmts].

So an iterator_range does not satisfy the requirements of SequenceT. If you pass my_str instead to to_upper_copy your code does compile. But if you want to only convert a sub-string, then you'll need to create a string containing that, and then call to_upper.

std::cout << boost::to_upper_copy(my_str) << std::endl; // works, but not a sub-string

std::string substr(rng2.begin(), rng2.end());
boost::to_upper(substr);
std::cout << substr << std::endl;

You can also avoid constructing the intermediate sub-string by using the to_upper_copy overload that takes an output iterator argument.

std::string result;
result.reserve(rng2.size());
boost::to_upper_copy(std::back_inserter(result), rng2);
std::cout << result << std::endl;

Live demo

Upvotes: 2

Related Questions