1

It is always frustrating when I try to declare an array in C++. Maybe I just do not understand how array works. The following situation is I am writing a constructor which initializes the row, col, and multi-dimensional array. The code runs into an error.

I already declared both row, col, and array variables in the private class members. When I run the main, the row and col will pass into constructor. The array should be initialized successfully?

#include <bits/stdc++.h>
using namespace std;

class SpreadSheet {
private:
  int r, c;
  string table[r][c];

public:
  SpreadSheet(int row, int col) {
    r = row; c = col;

    for (int i = 0; i < r; i++) {
      for (int j = 0; j < c; j++) {
        table[i][j] = ' ';
      }
    }
  }
};

int main() {
  SpreadSheet t(3, 3);

  return 0;
}

Below is the error log I got. I guess I understand the basic logic behind it. The array size has to be assigned before compiling the code. So what is the correct way around this problem?

demo.cc:7:16: error: invalid use of non-static data member ‘SpreadSheet::r’
    7 |   string table[r][c];
      |                ^
demo.cc:6:7: note: declared here
    6 |   int r, c;
      |       ^
demo.cc:7:19: error: invalid use of non-static data member ‘SpreadSheet::c’
    7 |   string table[r][c];
      |                   ^
demo.cc:6:10: note: declared here
    6 |   int r, c;
      |          ^
demo.cc: In constructor ‘SpreadSheet::SpreadSheet(int, int)’:
demo.cc:15:9: error: ‘table’ was not declared in this scope
   15 |         table[i][j] = ' ';
      |
domsupamoe
  • 39
  • 4
  • Offtopic: `table[i][j] = ' ';` – Any specific reason for inserting a space? Wouldn't you rather want to start with an empty string? (Not that having a space *always* is wrong, but without context, appears suspicious...) – Aconcagua Aug 24 '19 at 07:22
  • 2
    [Why should I not #include ?](https://stackoverflow.com/questions/31816095/why-should-i-not-include-bits-stdc-h/31816096) and `vector >` is a far better way to do a string table. – David C. Rankin Aug 24 '19 at 07:23
  • @Aconcagua yeah, it should be empty string. i didn't pay too much attention (xd – domsupamoe Aug 24 '19 at 07:43
  • @DavidC.Rankin thanks for suggestion. i will change my laziness habit – domsupamoe Aug 24 '19 at 07:45
  • @domsupamoe Be aware that for empty strings, you don't need explicit initialisation (`table[i][j] == ""`), the default constructor will initialise the object to the same anyway... – Aconcagua Aug 26 '19 at 14:43

4 Answers4

3
string table[r][c];

is invalid, as the compiler has already let you know.

r and c must be known at compile time for you to be able use them to declare an array.

You can use

std::vector<std::vector<std::string>> table;

and make sure to initialize it appropriately in the constructor of the class.

SpreadSheet(int row, int col) : table(row, std::vector<std::string>(col, " "))
{
 ...
}

If you use that, there is no need for the member variables r and c. Number of rows can be obtained by using table.size() and number of columns can be obtained using table[0].size() if number of rows is greater than zero.

The posted code for the class can be simplified to

class SpreadSheet {
  private:
    std::vector<std::vector<std::string>> table;

  public:
    SpreadSheet(int row, int col) : table(row, std::vector<std::string>(col, " ")) {}
};
Aconcagua
  • 19,952
  • 4
  • 31
  • 51
R Sahu
  • 196,807
  • 13
  • 136
  • 247
  • It is best to avoid a `vector` of `vector`s if it is a rectangular table. – Acorn Aug 24 '19 at 06:39
  • @Acorn what do you suggest? One longer vector? – Ayxan Haqverdili Aug 24 '19 at 06:41
  • @Ayxan A single `vector` for the entire table, yeah. – Acorn Aug 24 '19 at 06:42
  • 3
    @Acorn, not always. If you use a `vector` with size `r*c`, you'll have to deal with translating indices. The additional code complexity is not worthwhile for small vectors. – R Sahu Aug 24 '19 at 06:51
  • @RSahu On the other hand: I never managed to get a friend of abusing the function call operator as two-dimensional index operator; but for allowing a safe `SpreadSheet(10, 12)[7][7] = 7`, the efforts needed for (custom intermediate index class; for supporting begin/end, we'd need a custom iterator as well) make the index translation negligible... – Aconcagua Aug 24 '19 at 07:52
  • @RSahu Translating indices is literally a 1-line method. – Acorn Aug 24 '19 at 12:20
  • 1
    @psimpson `std::array` requires a compile time constant for it's size. So with that approach, you could only modify *one* dimension – or you'd create a hybrid between the vector of vector solution and the template for both dimensions proposed already. – Aconcagua Aug 26 '19 at 14:41
  • @Aconcagua erg I knew that ... once. I'll delete that comment so as not to confuse later generations. Thanks! – psimpson Aug 27 '19 at 00:38
0

VLAs (Variable Length Arrays) are not supported in C++ (and should not be used anyway in C).

The size of a variable must be known at compile-time, which implies you cannot define objects with unknown size (at compile-time), like the class in your example.

If you want a "dynamic array", use the standard std::vector. In particular, if you have a rectangular table, the best approach is to declare a:

std::vector<std::string> table;

And initialize/resize it for r * c elements.

Acorn
  • 22,093
  • 4
  • 30
  • 62
  • Wouldn't `string table[r][c];` from the question require a `vector >`? (`std::` excluded for brevity) – David C. Rankin Aug 24 '19 at 07:27
  • 1
    @DavidC.Rankin Not necessarily. A single vector would be more efficient, but requires appropriate index translation. If you wanted still to provide double indexing (`SpreadSheet(10, 12)[7][7]`), that would require an intermediate row index class, so quite some work to do... Your approach is simpler to handle in this respect, though. Thus I'd prefer it as first choice and switch over to single vector only if performance gets an issue... – Aconcagua Aug 24 '19 at 07:31
  • I got you. Taking advantage of the fixed `[c]` at the end. Yes definitely more efficient from a space standpoint, but wouldn't that required the compile time constant `[c]` to be known? The 1D approach, will be quite a bit more work, but can likely be optimized far greater than the 2D. – David C. Rankin Aug 24 '19 at 07:36
  • @DavidC.Rankin No, it does not require `c` to be constant. – Acorn Aug 24 '19 at 12:18
  • @Aconcagua Not necessarily, you just need a reusable method. – Acorn Aug 24 '19 at 12:22
  • @Acorn Now curious, how would that look like? – Aconcagua Aug 26 '19 at 09:59
  • @Aconcagua Simply have a `(c, r) -> index` method, and maybe have other methods to access the vector like `(c, r) -> T&`. Of course, the best is doing this once in a `vector2d` class and reusing it. – Acorn Aug 26 '19 at 13:59
  • @Acorn Oh, dropped that from my previous comment... I never managed to get a friend of abusing function call operator as double index operator. Instead, my comment was meant literally (*'**if** you want to provide `[][]`'*). Actually, this is what I consider far the cleanest interface, most resembling a classic 2D array. And *that* will require some work (unfortunately, there's no 2D index operator as in C#). Not suitable as high-performance interface, though. Somewhere in between the two operators (admitted, aesthetics is subjective...): a named function (`? get(size_t x, size_t y)`. – Aconcagua Aug 26 '19 at 14:27
  • @Aconcagua I wasn't referring to the `operator()` (I don't like that at all), I typically use `get` for unchecked access and `at` for checked. I don't really like using double-`operator[]` because it requires a wrapping type and more complexity for no advantage (and tries too much to mimic normal arrays). Anyway, I saw a paper arguing for deprecating of the comma operator inside `operator[]` so that in a future standard we may be able to finally use more than one argument in `operator[]`! – Acorn Aug 26 '19 at 18:33
-1

I wouldn't use a C style array like x[r][c]. The better solution is vector of vector as described in the answer by R Sahu.

However if you really want to use a C-style array, you can use a template like:

template <int r, int c> class SpreadSheet 
{
    string table[r][c];
};

int main() {
    SpreadSheet<3, 3> t;
    ...
4386427
  • 33,845
  • 4
  • 32
  • 53
  • It is best to avoid a `vector` of `vector`s if it is a rectangular table. – Acorn Aug 24 '19 at 06:39
  • @Acorn are there any reasons behind to avoid a `vector` of `vector` – domsupamoe Aug 24 '19 at 06:48
  • 2
    @domsupamoe Well, my guess is that Acron concerns may be about 1) a little extra memory usage due to the overhead of each vector and/or 2) performance concerns due to memory layout (aka cache). However, using a single vector (as Acron proposes) will make your code a bit more difficult to read as you need to handle the 2D indexing your self. If performance/memory is not a concern, I'll strongly suggest vector of vector. – 4386427 Aug 24 '19 at 06:56
  • 3
    Performance/memory should definitely not be a concern to a newbie (and not be a concern most of the time to an experienced programmer). Saying 'don't use this' without any explanation just leads to cargo cult programming. – john Aug 24 '19 at 07:17
  • @john You shouldn't be using C++ to begin with if performance is not a concern, though... Something like this is a performance pitfall in many applications. – Acorn Aug 24 '19 at 12:19
  • The edited answer is even worse than before. This requires compile-time constants, which is clearly not what was asked for. – Acorn Aug 24 '19 at 12:21
-1

Another solution would be a pointer instead of an array.

   class SpreadSheet {
private:
  int r, c;
  string **table;

public:
  SpreadSheet(int row, int col) {
    r = row; c = col;

    table = new string*[r];
    for (int i = 0; i < r; ++i)
    {
        table[i] = new string[c];
    }

    for (int i = 0; i < r; i++) {
      for (int j = 0; j < c; j++) {
        table[i][j] = ' ';
      }
    }
  }
};