4

Using C++, I want to create a for loop as follows (text is a std::string):

 for(int i = text.size(); i >= 0; i--) {
 {

Please could somebody help me understand why my complier (Xcode) requires me to initialise i as an unsigned long, and not as integer?

I assume the following, but I don't know, and I'd like to develop a better understanding: I believe it is to do with an integer having a maximum value of 2147483647, whereas an unsigned long has a maximum value of 18,446,744,073,709,551,615. Since a string can hold up to 4294967295 characters, the use of an integer is inappropriate since its maximum value couldn't represent the maximum length of a string?

Finally, is the unsigned long data type appropriate, or should I use a long int?

My amended for loop is as follows:

 for(unsigned long i = text.size(); i >= 0; i--) {
    }

or

for(long int i = text.size(); i >= 0; i--) {
    }
Adrian Mole
  • 30,672
  • 69
  • 32
  • 52
  • 1
    do you get a compiler error? Please incldue it in the question – 463035818_is_not_a_number Mar 05 '21 at 12:17
  • 3
    `texts.size()` returns an unsigned type. The real question: If you're enumerating the string in reverse order, why aren't you using `for (auto it = text.rbegin(); it != text.rend(); ++it)` , utilize the iterator as warranted, and be done with it? – WhozCraig Mar 05 '21 at 12:19
  • No, I don't get a complier error. However, I get a warning: Implicit conversion loses integer precision: 'std::__1::basic_string, std::__1::allocator >::size_type' (aka 'unsigned long') to 'int' – Ian Thompson Mar 05 '21 at 12:19
  • 2
    `size()` returns `size_t`. Its easiest to use just that, but beware that then `i >= 0; i--` won't do any good – 463035818_is_not_a_number Mar 05 '21 at 12:20
  • 1
    a warning is not the same as "my compiler wont let me", its rather "my compiler reminds me that most likely I should fix something" – 463035818_is_not_a_number Mar 05 '21 at 12:21
  • `i = text.size()` makes `i` start out of bounds so you must use `i - 1` inside the loop and in that case, your condition should be `i > 0`. - Alternatively: `for(size_t i = text.size() - 1; i < text.size(); --i)` – Ted Lyngmo Mar 05 '21 at 12:21
  • Hi WhorzCraig, I'm working my way through a beginner course and haven't reached those functions as of yet; I will have a look though, cheers. – Ian Thompson Mar 05 '21 at 12:23
  • Sorry guys, I should have made it clear. I'm completing a beginner course and thus far I'm only using basic operators. I understand that there are more efficient methods, however, for the task I have to complete, I'm required to use basic functions. Sorry, my bad. – Ian Thompson Mar 05 '21 at 12:28
  • `i >= 0` is always true for unsigned `i` – stark Mar 05 '21 at 12:30
  • Sounds like the beginner course is teaching **advance techniques** (or the *to be avoided techniques*, except for experts with years of experience in the language and know when the advanced technique is warranted) rather than the **basic techniques** such as the one WhozCraig mentioned. – Eljay Mar 05 '21 at 12:31
  • largest_prime_is_463035818: I see, thank you. Okay, I have a follow up question: If I proceed and compile, on this occasion the code functions because the length of the string is less than the maximum length of the integer. Even though we know the length of the string will never exceed the limits of the integer, is it okay to use the integer data type, or is it best practice to use a data type that matches the limits of the maximum string length? – Ian Thompson Mar 05 '21 at 12:41
  • In [C++ style guide](https://google.github.io/styleguide/cppguide.html#Integer_Types) it is mentioned: *try to use iterators and containers rather than pointers and sizes, try not to mix signedness, and try to avoid unsigned types (except for representing bitfields or modular arithmetic). Do not use an unsigned type merely to assert that a variable is non-negative.*. If you can't use iterators here, use signed types. If needed, use *casting* to avoid warnings. – Damien Mar 05 '21 at 12:43
  • Eljay: Using the length of a string to determine the number of for loop iterations is an advanced technique, surely not? – Ian Thompson Mar 05 '21 at 12:44
  • I didn't want to imply that you should not take warnings serious. not at all. Best is to treat them as errors and fix whatever made the compiler complain. Also, never say never, its always better to not write code that only works due to implicit assumptions. A string whose lenght exceeds the range of `int` is unlikely, but it is such kind of assumptions that can cause big trouble on the long run – 463035818_is_not_a_number Mar 05 '21 at 12:48
  • Damien, you're a legend, thank you! This is exactly what I'm after. I already had the C++ Style Guide bookmarked, I really should have looked at this. Note to self: refer to the C++ style guide before asking questions. Thanks again. – Ian Thompson Mar 05 '21 at 12:49
  • Largest_Prime: I'll ensure I keep this in mind. I appreciate the help and pointers, thanks a lot. – Ian Thompson Mar 05 '21 at 12:52
  • tip: prepend the user name with an @ so they get notified. I saw your comment only by chance – 463035818_is_not_a_number Mar 05 '21 at 12:53

2 Answers2

7

std::string::size() returns an unsigned integer type (std::size_t). Changing the type of i is easy enough, but introduces another problem. With i being type std::size_t the loop condition is broken; by definition an unsigned type is always going to be >= 0

There are alternatives, the most direct being to modify the entire for-loop to:

  1. Declare i as std::size_t
  2. Move the decrement as a post-decrement in the condition check
  3. Remove the increment step entirely.

The result looks like this:

for (std::size_t i = text.size(); i-- > 0;)
{
    // use text[i] here
}

This will enumerate within the loop body from (text.size()-1) through 0 inclusively, and break the loop thereafter. It will work even if the string is empty.

Note that such hijinks are a sign of a larger problem: using the wrong construct in the first place. std::string provides reverse iterators for walking the string content backward:

for (auto it = text.rbegin(); it != text.rend(); ++it)
{
    // use *it  to access each character in reverse order
}

Of these two methods, the second is preferred.

WhozCraig
  • 59,130
  • 9
  • 69
  • 128
3

As noted in the comments, the size() member function of the std::string class returns a value of type size_t. The exact nature of this type will vary between platforms and compilers, but it will always be an unsigned type. Generally, it is equivalent to either unsigned long int or unsigned long long int.

So, to avoid the compiler warning about initialising your int i, you should declare that loop index as a size_t variable: for (size_t i = text.size(); ....

However, as also noted in the comments, your loop test condition (i >= 0) is then no good, because, for any unsigned type, that condition can never be false. So, instead, you should change that condition to i > 0; you should also be sure that any access to your test elements based on that i variable should use the index value i - 1 (rather than just i): the last element will be at size - 1 and the first element will be at 0, so your loop will hold for the following:

for (size_t i = text.size(); i > 0; --i) {
    char element = text[i - 1]; // We need to offset our loop index by -1 ...
    // Do something with or to 'element'
    // ...
}
Adrian Mole
  • 30,672
  • 69
  • 32
  • 52