c++c++11stdstringconst-iterator

Get a non-const iterator from a const string parameter


I am reading strings with the following syntax:

string example {"firstList:[element1:value1,element2:value2];secondList:[elementA:valueA,elementB:valueB]"};

Since a real list can have hundreds of elements, and I need only read operations (checking if value1 == "something" or if elementA exists), I implemented a std::string::iterator based function to find a list inside a given string. Here is a simplified version:

bool findList(
    const std::string &input,
    const std::string &listName,
    std::string::iterator globalStart,
    std::string::iterator globalEnd,
    std::string::iterator &listStart,
    std::string::iterator &listEnd
    )
{
    if (input.empty() || listName.empty()) return false;

    size_t inicio = input.find(listName);
    if (inicio == string::npos) return false;

    size_t fin = input.find("]", inicio);
    if (fin == string::npos) return false;

    listStart = globalStart + inicio + listName.length() + 2;
    listEnd = globalStart + fin;
    if (listEnd >= globalEnd) return false;

    string::iterator badList = std::find(listStart, listEnd, ';');
    if (badList != listEnd) return false;

    return true;
}

The function works, but I have to pass the string input, input.begin(), and input.end() to assign values for the list iterators. Is there another option to assign the input.begin() to listStart (and the same for listEnd)?

Writing:

    listStart = input.begin() + inicio + listName.length() + 2;
    listEnd = input.begin() + fin;

yields a compiler error for trying to assign a const iterator to a non-const iterator:

error: no match for ‘operator=’ (operand types are ‘std::__cxx11::basic_string<char>::iterator {aka __gnu_cxx::__normal_iterator<char*, std::__cxx11::basic_string<char> >}’ and ‘__gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char> >’)
  listStart = input.begin() + inicio + listName.length() + 2;

I am aware that passing the input only as std::string &input solves the problem, but I would like to keep the checks that const entails.

Here is a minimal example:


#include <iostream>
#include <string>
#include <algorithm>
#include <assert.h>

using std::cout;
using std::endl;
using std::string;
using std::size_t;

bool findList(
    const std::string &input,
    const std::string &listName,
    std::string::iterator globalStart,
    std::string::iterator globalEnd,
    std::string::iterator &listStart,
    std::string::iterator &listEnd
    )
{
    if (input.empty() || listName.empty()) return false;

    size_t inicio = input.find(listName);
    if (inicio == string::npos) return false;

    size_t fin = input.find("]", inicio);
    if (fin == string::npos) return false;

    listStart = globalStart + inicio + listName.length() + 2;
    listEnd = globalStart + fin;
    if (listEnd >= globalEnd) return false;

    string::iterator badList = std::find(listStart, listEnd, ';');
    if (badList != listEnd) return false;

    return true;
}

int main() {
    string example {"firstList:[element1:value1,element2:value2];secondList:[elementA:valueA,elementB:valueB]"};
    string::iterator listStart, listEnd;
    bool answer = false;

    // Test the findList function
    answer = findList(example, "firstList", example.begin(), example.end(), listStart, listEnd);
    assert(answer == true);
    assert(string(listStart, listEnd) == "element1:value1,element2:value2");
    cout << "Test firstList passed" << endl;

    answer = findList(example, "secondList", example.begin(), example.end(), listStart, listEnd);
    assert(answer == true);
    assert(string(listStart, listEnd) == "elementA:valueA,elementB:valueB");
    cout << "Test secondList passed" << endl;

    answer = findList(example, "thirdList", example.begin(), example.end(), listStart, listEnd);
    assert(answer == false);
    assert(string(listStart, listEnd) == "elementA:valueA,elementB:valueB"); // iterators remain unchanged
    cout << "Test thirdList passed" << endl;

    return 0;
}

*This is my first question, please feel free to point out any improvement I could make to the post.


Solution

  • const std::string::iterator means "iterator for a string, through which the string can be changed; the iterator can't be pointed anywhere else."

    std::string::const_iterator means "iterator for a string, through which the string cannot be changed; the iterator can be pointed somewhere else."

    It looks like you are confusing these two. input.begin() returns a const_iterator when input is const, and you can't assign a const_iterator to an iterator -- that would break const-correctness.

    All of your iterators in this function should be const_iterator. Change your function signature to:

    bool findList(
        const std::string &input,
        const std::string &listName,
        std::string::const_iterator globalStart,
        std::string::const_iterator globalEnd,
        std::string::const_iterator &listStart,
        std::string::const_iterator &listEnd
    ) {
        // ...
    }
    

    The caller should then use str.cbegin() and str.cend(). Likewise, listStart and listEnd in main() should be changed to be const_iterator.