How do you declare and create a 2-dimensional array whose dimensions are constant and known at compile time, but are specified by literal const arguments to the constructor of the class that owns it?
For example...
Foo.h:
class Foo {
public:
Foo(int rows, int cols);
private:
int totalRows;
int totalCols;
char buf[4][20]; // I don't actually WANT to hardcode 4 and 20 here!
};
Foo.cpp:
Foo::Foo(const int rows, const int cols) : totalRows(rows), totalCols(cols), buf(new char[rows][cols]) {}
Main.cpp:
Foo myFoo(4,20);
I know buf(char[rows][cols]) is totally wrong... it's just there to illustrate what I'm trying to achieve.
I'm pretty sure I somehow have to use constructor-initialization syntax (like I'm using to set the values of totalRows and totalCols) ... but, for arrays, I'm not sure what that syntax actually is. I'm not sure whether such syntax even exists for array-declaration, since the use case of "constructor with const int args that can only be invoked with literals to guarantee the values are known at compile-time and thus suitable for an array declaration" is admittedly kind of an extreme edge case.
In Java, I'd just declare a char[] named buf whose value is implicitly unassigned at declaration-time, then create (and assign) it in the body of the constructor ... but, as far as I know, C++ doesn't allow that, either.
I know I could probably sidestep the issue by making buf a char* and creating it in the constructor via malloc(rows * cols) ... but that seems kind of barbaric and just seems "bad" for some reason I can't quite put my finger on.
Update
OK, it looks like @Adrian Mole's idea of using templates is the right one, but now I have a new problem... because Foo is now a Foo<4,20>, the compiler won't allow me to specify Foo or Foo* as a method or constructor parameter type... it wants them all to be explicitly specified, too.
The above notwithstanding, I just discovered that Foo.cpp now has an even bigger problem. When I try implementing Foo's other methods (which I suppose I should have mentioned), the compiler is rejecting them because "Foo is not a class, namespace, or declaration". For example, the following line now gets rejected:
int Foo::getTotalRows() {
return totalRows;
}
... presumably, because there's no longer a class Foo. Except, it looks like I can't even temporarily hack around that by inserting <4,20> (eg, int Foo<4,20>::getTotalRows()).
new Main.cpp:
Foo<4,20> myFoo;
Bar firstBar = new Bar(&myFoo, 2,17,4);
Bar.h:
class Bar {
public:
Bar(Foo<4,20>* srcFoo, int row, int col, int len);
// ... snip ...
private:
char* chars;
int length;
}
Bar.cpp:
{
Bar::Bar(Foo<4,20>* srcFoo, int row, int startCol, int length)
: chars(*srcFoo->getBuf(row, startcol, length), length(length) {}
// ... snip ...
}
... which kind of defeats the point, because it means I've gone from having to hardcode the 4 and 20 in one place, to having to hardcode it in every single place I subsequently make use of a Foo object.
Unless... there's a way to indicate to the compiler that a method/constructor should match any variant of a templated class, and have the compiler simply auto-generate the specific flavor at compile-time. For example, something like...
Bar(Foo<>* pFoo, int row, int col, int len);
... so that if Main.cpp does something like:
Foo<4,20> myFoo;
Bar first(&myFoo,1,17,3);
... the compiler would say, "OK, I know myFoo is a Foo<4,20>, and he's calling Bar's constructor. Bar has constructor that takes any flavor of Foo<>, so I'll just pretend he declared it in the source as:
Bar(Foo<4,20>* pFoo, int row, int col, int len);
... and continue normally. Then, further down, if I did:
Foo<2,16> smallerFoo;
Bar second(&smallerFoo, 0,3,11);
... the compiler would say, "OK, smallerFoo is a Foo<2,16>. Bar's Foo*-taking constructor matches, but since we haven't used a Foo<2,16> yet, I'll have to autogenerate another set of methods and pretend the source really said something like:
Bar(Foo<4,20>* pFoo, int row, int col, int len);
Bar(Foo<2,16>* pFoo, int row, int col, int len);
In other words, have the compiler recursively apply the templating to auto-generate matching methods as well.
Does something like this exist, or do I have to go back to the drawing board unless I want to explicitly specify the <rows,cols> in every subsequent use of a Foo object (including method parameters)?
rowsandcolscan't be used to specify the size of the array. It doesn't matter whether they'reconstparameter or not. Usestd::vectorif possible.Fooa class template, when the line inmainwould look something likeFoo<4, 20> myFoo;. Is that what you're after?bufis a 2D array, so why are you usingnewin the mem-initializer list?std::vectororstd::string), or roll your own (which would be a fools errand since they already exist). The one thing you will lose when using containers of containers is continuity of data, so if that is some unstated requirement, you'll be forced to invent.Foo(10, 10)andFoo(4, 666)have the same type? If yes then you needneworstd::vector. If not then template andstd::arrayare the way to go.