c++vector

What does the error 'return': cannot convert from 'std::_Vb_reference<std::_Wrap_alloc<std::allocator<std::_Vbase>>>' to 'T &' mean in C++?


I am very new to programming, and to learn I'm using Bjarne Stroustrup's Programming, Principles and Practice using C++, Second Edition. For the first few chapters, the author provides a header file for the students' use, called "std_lib_facilities.h" (link).

In solving one of the exercises, I wrote a (for my standards) pretty complicated program with a few functions that uses functions from the header file.

#include "../../../std_lib_facilities.h"

bool vector_has_distinct_values(vector<short> v) //checks if the passed vector has distinct values
//no pre- or postconditions necessary for this function because it makes no assumptions about its input (other than that
//it is composed of a vector of type short int, which the caller anyway checks), and it can only output true or false
//(both reasonable values)
{
    sort(v);
    for (int x = 0; x < v.size(); ++x)
    {
        if (x > 0 && v[x] == v[x - 1])
            return false;
    }
    return true;
}

int how_many_bulls_and_cows(vector<short int> answers, vector<short int> guesses)
//we assume that both vectors contain values >= 1 and <= 9, and that they have distinct values
//nb: in the answer generation function, we already checked that the answers vector has distinct values, so it's ok not to check it here
{
    int bulls = 0, cows = 0;
    if (!vector_has_distinct_values(guesses)) //check a pre-condition
        throw invalid_argument("Your guesses should be different numbers.");  //for distinct error handling by the caller
    for (int i = 0; i < answers.size(); ++i)
    {
        if (answers[i] < 0 || answers[i] > 9)                                 //more pre-condition checks
            throw runtime_error("bad answer generation (OOB values)");
        if (guesses[i] < 0 || guesses[i] > 9)
            throw runtime_error("the numbers must be single-digit, positive integers");
        for (int j = 0; j < guesses.size(); ++j)
        {
            if (guesses[j] == answers[i] && i == j)
                bulls++;
            else if (guesses[j] == answers[i])
                cows++;
        }
    }
    if (bulls < 0 || bulls > 4 || cows < 0 || cows > 4) //we are checking the post condition that -1 < bulls < 5 and -1 < cows < 5
        throw runtime_error("unexpected number of bulls and/or cows"); 
    return bulls * 10 + cows;
}

vector<short> generate_answers(vector<short> answers)
//we assume that answers is empty
{
    if (answers.size() > 0)
        throw runtime_error("the answers vector should be empty before answer generation");
    vector<bool> has_this_number_been_generated(10);
    short aux;
    for (int i = 0; i < 4; ++i)
    {
        while (true) {
            aux = randint(10);
            if (!has_this_number_been_generated[aux])
            {
                answers.push_back(aux);
                has_this_number_been_generated[aux] = true;
                break;
            }
        }
    }
    if (!vector_has_distinct_values(answers))
        throw runtime_error("bad answer generation (repeating values)");
    return answers;
}

int main()
{
    vector<short> answers(4);
    vector<short> guesses(4);
    int bulls, cows;
    int counter = 0;
    bool first_time = true;
    while (true)
    {
        if (first_time)
        {
            cout << "Welcome to bulls and cows!\n";
        }
        if (counter == 0)
        {
            answers = generate_answers(answers);
        }
        cout << "What are your guesses? ";
        try {
            for (int i = 0; i < guesses.size(); ++i)
            {
                cin >> guesses[i];
                if (!cin)
                    throw runtime_error("guesses should be positive, distinct, single-digit integers");
            }
            ++counter;
            int b_and_c = how_many_bulls_and_cows(answers, guesses); // this variable avoids unnecessary calls to this function
            bulls = b_and_c / 10;
            cows = b_and_c % 10;
        }
        catch(runtime_error& e) {
            cerr << "error: " << e.what() << '\n';
            return 1;
        }
        catch (invalid_argument& e) {                           
            cerr << "tip: " << e.what() << ". Try again!\n";  
            continue;
        }
        if (bulls == 4)
        {
            cout << "Congratulations, 4 bulls! You win after " << counter << " guesses!\n";
            first_time = false;
            char answer = 0;
            cout << "Play again? ('y' or 'n')";
            try {
                cin >> answer;
                if (!cin)
                    throw runtime_error("invalid answer (answer must be a *character* among 'y' or 'n'");
                if (answer == 'y')
                {
                    counter = 0;
                    continue;
                }
                else if (answer == 'n')
                    break;
                else
                    throw runtime_error("invalid answer (answer should be 'y' or 'n')");
            }
            catch (runtime_error& e) {
                cerr << "error: " << e.what() << '\n';
                cout << "Goodbye!\n";
                return 1;
            }
        }
        cout << bulls << " bull(s) and " << cows << " cow(s)\n";
    }
    cout << "Goodbye!\n";
    return 0;
}

It is essentially an implementation of the game Bulls and Cows that attempts to catch all possible errors (it's in the chapter on error management).

Unfortunately, this code does not compile, giving me the following error: 'return': cannot convert from 'std::_Vb_reference<std::_Wrap_alloc<std::allocator<std::_Vbase>>>' to 'T &'

This error references line 95 in "std_lib_facilities.h"

Being a beginner, this language is very cryptic to me and I don't understand what it means, or how I could fix this error. I am certain it's not the header file's fault (it's probably my program that's interacting with it in an unexpected/bad way), but that doesn't help me fix it, and I can't understand it.

Of course, I welcome any critique of my code, as it is a very valuable learning experience for me. Could somebody please explain the problem to me?

EDIT: This is the error-producing segment:

#ifdef _MSC_VER
    // microsoft doesn't yet support C++11 inheriting constructors
    Vector() { }
    explicit Vector(size_type n) :std::vector<T>(n) {}
    Vector(size_type n, const T& v) :std::vector<T>(n,v) {}
    template <class I>
    Vector(I first, I last) : std::vector<T>(first, last) {}
    Vector(initializer_list<T> list) : std::vector<T>(list) {}
#else
    using std::vector<T>::vector;   // inheriting constructor
#endif

    T& operator[](unsigned int i) // rather than return at(i);
    {
        if (i<0||this->size()<=i) throw Range_error(i);
        return std::vector<T>::operator[](i);
    }
    const T& operator[](unsigned int i) const
    {
        if (i<0||this->size()<=i) throw Range_error(i);
        return std::vector<T>::operator[](i);
    }
};

The specific line that produces the error is the first return std::vector<T>::operator[](i);


Solution

  • You have had the misfortune to discover that vector<bool> does not behave the same as other vectors do. vector<bool> has been specialized to save space and turns all of its bools into bits and packs those bits into an array of small integers. Usually you get at least an 8:1 space savings.

    But this comes at a cost. For example, when you use [], you don't get a reference to the bool stored in the vector like you do with every other vector, because it's not possible to reference a single bit, you get a temporary proxy object that represents the bool. This proxy object then wrecks havoc because it cannot be referenced.

    Specifically what went wrong is in the vector wrapper buried inside std_lib_facilities.h we find

    T& operator[](unsigned int i) // rather than return at(i);
    {
        if (i<0 || this->size() <= i) throw Range_error(i);
        return std::vector<T>::operator[](i);
    }
    

    which tries to return the proxy by reference. The compiler immediately slaps that down because the proxy will go out of scope and become invalid before anyone can possibly use it. In proper language, the proxy is an rvalue and you cannot refer to an rvalue.

    There are a variety of solutions, but most deal in not using vector<bool> for this task. Typical candidates are use a vector<uint8_t> (or a vector of any other integer type), a std::set<int> or std:unordered_set<int> to allow rapid look-up to see if an int value is already in the set (potentially much faster than vector for large or sparse lists), but since we know the range of possible values, this range is small, and space is not at a premium I think std::array<bool, 10> is the best fit for the job.