tags:

views:

135

answers:

3

I'm making a simple command line Hangman game.

void Hangman::printStatus()
{
    cout << "Lives remaining: " << livesRemaining << endl;
    cout << getFormattedAnswer() << endl;
}

string Hangman::getFormattedAnswer()
{
    return getFormattedAnswerFrom(correctAnswer.begin(), correctAnswer.end());
}

string Hangman::getFormattedAnswerFrom(string::const_iterator begin, string::const_iterator end)
{
    return begin == end? "" : displayChar(*begin) + getFormattedAnswerFrom(++begin, end);
}

char Hangman::displayChar(const char c)
{
    return c;
}

(Eventually, I'll change this so displayChar() displays a - or a character if the user has guessed it, but for simplicity now I'm just returning everything.)

When I build and run this from VS 2010, I get a popup box:

Debug Assertion Failed!

xstring Line: 78

Expression: string iterator not dereferenceable

What am I doing wrong?

A: 

If correctAnswer is empty, correctAnswer.begin() will be the same as correctAnswer.end() and not be dereferenceable.

fbrereto
But shouldn't it immediately terminate the recursion then?
jpalecek
He has a check for that. Are you saying that even if begin == end, displayChar(*begin) + getFormattedAnswerFrom(++begin, end); will still be computed?
Duracell
@jpalecek: Ah yes, that's true.
fbrereto
A: 

It seems fine to me. However, remember any heap or stack corruption could produce this error. You need to grab the stack trace and look inside correctAnswer, and ensure that both it and the instance of Hangman in question are valid objects.

Also, I'm just flat out a little concerned about your functions here. They seem very strange. Why not just replace with std::for_each?

Edit@comment:
If you have C++0x, you can just do

std::for_each(correctAnswer.begin(), correctAnswer.end(), [this](const char& ref) {
    std::cout << this->displayChar(ref);
});

Else, you will have to do something that looks a little like this:

struct helper {
    Hangman* ptr;
    void operator()(const char& ref) {
        std::cout << ptr->displayChar(ref);
    }
};
helper Helper;
Helper.ptr = this;
std::for_each(correctAnswer.begin(), correctAnswer.end(), Helper);
DeadMG
Can you show me a solution using for_each?
Rosarch
Edited in. You shouldn't have problems with that.
DeadMG
Intriguing. Can you explain the syntax of the top example? What's the significance of `[this]`?
Rosarch
When using lambda captures, [this] is a special dispensation where the this pointer is captured by value, and the resulting lambda is considered to be a member lambda, effectively.http://msdn.microsoft.com/en-us/library/dd293599.aspx#methodLambdaExpressions Strictly, I didn't have to dereference this inside the body, but did anyway.
DeadMG
+8  A: 

The problem is in the evaluation of:

displayChar(*begin) + getFormattedAnswerFrom(++begin, end)

In executing this statement, it is evident that your compiler is first incrementing begin, returning the "next" begin for use as the first argument to getFormattedAnswerFrom, and then dereferencing begin for the argument to displayChar.

When begin is one behind end, then begin != end so displayChar(*begin) + getFormattedAnswerFrom(++begin, end) will run. Your compiler increments begin, so now begin == end, and the dereference of begin is invalid.

See also: http://stackoverflow.com/questions/2934904/order-of-evaluation-in-c-function-parameters/2934965#2934965

Daniel Trebbien
Does the C++ spec define the order of evaluation of sub-expressions of binary operators or is it just common to go right-to-left, as your assert?
David Gladfelter
+1: Undefined behaviour, how could I miss it...
jpalecek
@David: It's UB, at least for primitive types. For user types it's unspecfied, but could be undefined, too (I don't know exactly).
jpalecek
@David, I don't think that the C++ standard specifies whether `*begin` or `++begin` will execute first. I didn't intend to assert right-to-left evaluation of subexpressions, so I have updated my answer.
Daniel Trebbien
In fact, I once worked at a company that used the Intel *and* Visual C++ compilers. The Intel compiler tended to evaluate left-to-right and the Visual C++ compiler tended to evaluate right-to-left. This led to a few issues where some test cases would succeed if compiled with `icc` but fail if compiled with `cl`.
Daniel Trebbien
I thought that plain order of operations went left-to-right?In any case, this is yet another example of why ++x or x++ should be avoided if used as part of a greater expression.
DeadMG