Struggling with getting passed pointer to a double array type string. What am I doing wrong?

Hello. I am trying to get past "basic" functions in C++ and onto objects and passing pointers to manipulate array data.
I am passing a csv file into my program, reading it and trying to manipulate it as I need and am running into an error when I compile (using Linux, Debian Stable). The results of compile are as follows:
1
2
3
4
5
6
7
8
9
# g++ -Wall csv_object.cpp -o csv_object.out
csv_object.cpp: In function ‘int main(int, char**)’:
csv_object.cpp:171:40: error: no matching function for call to ‘csv::SpreadArray(std::__cxx11::string [rows][columns])’
      raw_file.SpreadArray(working_array);          // send the working_array ptr to the class function and perform it's function
                                        ^
csv_object.cpp:102:16: note: candidate: ‘void csv::SpreadArray(std::__cxx11::string*)’
           void SpreadArray(string* ptrArray)     //spread [0][0] to [rows][columns] based on delimiter
                ^~~~~~~~~~~
csv_object.cpp:102:16: note:   no known conversion for argument 1 from ‘std::__cxx11::string [rows][columns]’ {aka ‘std::__cxx11::basic_string<char> [rows][columns]’} to ‘std::__cxx11::string*’ {aka ‘std::__cxx11::basic_string<char>*’}


I have a defined string array (working_array[rows][columns]) I am passing the pointer address (working_array) into a class defined function called SpreadArray. ALL OTHER DEFINED FUNCTIONS WORK, VOID AND RETURN VALUES, but for some reason this generates and error. I am keeping them out of the code, but they are simply either void functions are counter return int functions. I am trying to understand what I am doing wrong or what I need to be aware of to prevent this in the future. Thank you for any feedback. PS. I use a lot of documentation while I code, so I apologize if it's too much.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <fsteam>
#include <string>

using namespace std;

class csv                //decleration
{
private:

public:
     void SpreadArray(string* ptrArray)     //spread [0][0] to [rows][columns] based on delimiter
          {
               cout << "testing " << *(ptrArray+1);
          }
};
int main( int argc, char *argv[] )
{
     // execution CMD PROMPT:
     //                ./filename.out <filename.csv>  LINUX
     //                  filename.exe <filename.csv>  WINDOWS
     csv raw_file;  //define the variable based on csv class
     unsigned int columns = raw_file.intGetTheColumns(import_file,delimiter); // read file, returns colums (working)
     unsigned int rows = raw_file.intGetTheRows(working_file); // read the file, returns rows (working)
     string working_string;
     string working_array[rows][columns] = {}; //define the empty matrix
     ifstream input_file1(import_file); // read the file
     while (input_file1)
     {
          getline(input_file1, working_string); // read each line of the file
          if (working_string.size() != 0)  //if line isn't blank
          {
               working_array[intRowCount][intColumnCount] = working_string;  //dump the whole string in string array [intRowCount][0]
               intRowCount++;  // move to the next row / line read from file
          }
     }
     cout << "address=" << working_array <<  endl; // me making sure it is is passing a pointer
     raw_file.SpreadArray(working_array);          // send the working_array ptr to the class function and perform it's function
return 0;
}


If I define the function as
void SpreadArray( void* ptrArray )
I can pass the pointer to the function but am unable to see the contents of the array beyond memory addresses or manipulate a void array(as the compiler warns)

What is the problem?
Why does it pass on void* but not string* (which should be a pointer to a stringed doulbe array [x][y]?
Is this something specific to strings or double array strings or what?

Thank you again.
Last edited on
I thought maybe it was a protoype error, but that just generated more compiler errors. Is there a best practice to put the function definition OUTSIDE the class? Would those be declared BELOW int main()?
All code for reference
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

class csv                //decleration
{
     private:

     public:
          /////////////////////////////////////////////////////////////////////
          string import(string filename)                   //read a raw filename, dump/return the contents as string
          {         // take in a filename and convert to a string
                    string input_string, output_string;
                    ifstream input_file(filename);

                    while (input_file)
                    {
                         getline(input_file,input_string);
                         output_string += input_string + "\n";
                    }
                    return output_string;                      // return whole string object in raw variable instead of open file

          } // end IMPORT function


          /////                  INTENTIONALLY BLANK                     //////

          bool delimiter_check(string filename, char delimiter)
          {
               string input_string;
               int delim_counter, delim_verify, row_counter;
               ifstream input_file(filename);               // open the filename, passed in
                    getline(input_file,input_string);       // read the file

               for (unsigned int c=0; c!=input_string.size();c++)  // run once on the "header/first line" to check the column count
               {
                    if (input_string.at(c) == ',')
                         delim_counter++;
               }
               delim_verify = delim_counter;      // set verify based on columns in header or first line of file.csv
               delim_counter = 0;
               row_counter = 1;
               while (input_file)
               {
                         getline(input_file, input_string);  // check the rest of the columns to see if they have the same column counts per line
                         row_counter++;
                         if (input_string.size() != 0) // if the file read line is a blank line, do not apply this
                         {
                              for (unsigned int c=0; c!=input_string.size();c++)
                              {
                                   if (input_string.at(c) == ',')
                                        delim_counter++;
                              }  // count the columns
                              if (delim_counter != delim_verify)  //if delimiter counter, per line, is different than the first line, return false.
                                   {
                                        cout << "Error in row " << row_counter << endl;  // Display the row error number to help the end user
                                        return false;  // fail delimiter consitency check
                                   }
                              else
                                   delim_counter = 0;
                         }
               }
               return true;
          }

          /////                  INTENTIONALLY BLANK                     //////

          unsigned int intGetTheRows(string str1)                         // pass raw string into structure, read \n to get row count
          {
               int return_counter = 0;
               for (unsigned int c=0; c!=str1.size(); c++)                 // loop thorugh the string, char by char until size of string hit
               {
                    if (str1[c] == '\n')
                         if (str1[c-1] != '\n') {return_counter++;}        // don't count if the character before is a \n, such as the end of file
               }
               return return_counter++;

          } // end intGetTheRows function

          /////                  INTENTIONALLY BLANK                     //////

          unsigned int intGetTheColumns(string filename, char delimiter)
          {
               int delim_count=0;
               string first_row;
               ifstream read_header(filename);
                    getline(read_header,first_row);

               for (unsigned int c=0; c!=first_row.size(); c++)
                    {
                         if (first_row.at(c) == delimiter)
                              delim_count++;
                    }
               return delim_count+1;
          } // end intGetTheColumns function


          /////                  INTENTIONALLY BLANK                     //////

          void SpreadArray(string* ptrArray)     //spread [0][0] to [rows][columns] based on delimiter
          {
               cout << "testing " << *(ptrArray+1);
          }

          void output(string output_string1)
          {
               cout << output_string1;
          } // end output function

          /////                  INTENTIONALLY BLANK                     //////

};


int main( int argc, char *argv[] )
{
     // execution CMD PROMPT:
     //                ./filename.out <filename.csv>  LINUX
     //                  filename.exe <filename.csv>  WINDOWS

     /////                    FILE AVAILABILITY CHECK               //////

     if (argc !=2) // argc should be 2 for correct execution
     {
          cout << "run filename.out <filename.csv>" << argv[0] << endl;
          return 3;
     }
     cout << "argv=" << argv[1] << endl;
     ifstream input_file( argv[1] );
     if ( !input_file.is_open() )
          { cout << "File not found or is currently locked from reading. EXITING ..." << endl; return 3; }
     else
          input_file.close();


     /////      START OF PROGRAM AFTER FILE AVAILABILITY CHECK      //////

     string import_file = argv[1];
     char delimiter = ',';
     csv raw_file;
     string working_string;
     string working_file = raw_file.import(import_file);

     if (!raw_file.delimiter_check(import_file, delimiter))   // check to make sure all the columns are all the same throughout the file.
     {
          cout << "EXITING PROGRAM. A problem occured in the file. The delimiter count/column count wasn't equal throughout the file" << endl;
          cout << "Please inspect the file, correct the issue and try again." << endl;
          return 3;
     }
     else
          cout << "The file has passed it's column/delimiter consistency checking.  Continuing..." << endl;

     unsigned int columns = raw_file.intGetTheColumns(import_file,delimiter);
     unsigned int rows = raw_file.intGetTheRows(working_file);
     string working_array[rows][columns] = {};
     unsigned int intRowCount = 0, intColumnCount = 0;

     ifstream input_file1(import_file);
     while (input_file1)
     {
          getline(input_file1, working_string);
          if (working_string.size() != 0)
          {
               working_array[intRowCount][intColumnCount] = working_string;  //dump the whole string in string array [n][0]
               intRowCount++;  // move to the next row / line read from file
          }
     }
     cout << "address=" << *(working_array+1) <<  endl;
     raw_file.SpreadArray(working_array);          // send the working_array ptr to the class function and perform it's function

return 0;
}
First this line:

string working_array[rows][columns] = {};
Should fail to compile since rows and columns are not compile time constants. C++ doesn't allow VLA.

Second working_array is not a pointer it is a multidimensional array.

I would recommend that you use a std::vector instead of the arrays.

The code can be somewhat simplified. Also there are some issue. Consider as C++17 (compiles OK but not tested):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <vector>

using namespace std;

using Array = vector<vector<string>>;

class csv {
public:
	//read a raw filename, dump/return the contents as string
	string import(const string& filename) {
		string output_string;
		ifstream input_file(filename);

		for (string input_string; getline(input_file, input_string); output_string += input_string + "\n");
		return output_string;
	}

	bool delimiter_check(const string& filename, char delimiter = ',') {
		ifstream input_file(filename);
		string input_string;

		getline(input_file, input_string);

		const auto delim_verify {count(input_string.begin(), input_string.end(), delimiter)};

		for (size_t row_counter {1}; getline(input_file, input_string); ++row_counter)
			if (!input_string.empty())
				if (const auto delim_counter {count(input_string.begin(), input_string.end(), delimiter)}; delim_counter != delim_verify)
					return (cout << "Error in row " << row_counter << '\n'), false;

		return true;
	}

	size_t intGetTheRows(const string& str1) {
		size_t return_counter {};

		for (size_t c {}; c != str1.size(); ++c)
			if (str1[c] == '\n')
				// don't count if the character before is a \n, such as the end of file or at start
				if (c > 0 && str1[c - 1] != '\n')
					++return_counter;

		return return_counter++;
	}

	size_t intGetTheColumns(const string& filename, char delimiter) {
		string first_row;
		ifstream read_header(filename);

		getline(read_header, first_row);
		return count(first_row.begin(), first_row.end(), delimiter) + 1;
	}

	//spread [0][0] to [rows][columns] based on delimiter
	void SpreadArray(const Array& array) {
		cout << "testing " << array[0][0] << '\n';
	}

	void output(const string& output_string1) {
		cout << output_string1 << '\n';
	}
};

int main(int argc, char* argv[])
{
	// execution CMD PROMPT:
	//                ./filename.out <filename.csv>  LINUX
	//                  filename.exe <filename.csv>  WINDOWS

	// argc should be 2 for correct execution
	if (argc != 2)
		return (cout << "run filename.out <filename.csv>" << argv[0] << '\n'), 3;

	cout << "argv=" << argv[1] << '\n';

	ifstream input_file(argv[1]);

	if (!input_file)
		return (cout << "File not found or is currently locked from reading. EXITING ...\n"), 3;
	else
		input_file.close();

	/////      START OF PROGRAM AFTER FILE AVAILABILITY CHECK      //////

	constexpr char delimiter {','};
	const string import_file {argv[1]};
	csv raw_file;
	const string working_file {raw_file.import(import_file)};

	// check to make sure all the columns are all the same throughout the file.
	if (!raw_file.delimiter_check(import_file, delimiter)) {
		cout << "EXITING PROGRAM. A problem occured in the file. The delimiter count/column count wasn't equal throughout the file\n";
		cout << "Please inspect the file, correct the issue and try again.\n";
		return 3;
	} else
		cout << "The file has passed it's column/delimiter consistency checking.  Continuing...\n";

	const auto columns {raw_file.intGetTheColumns(import_file, delimiter)};
	const auto rows {raw_file.intGetTheRows(working_file)};

	vector<vector<string>> working_array(rows);

	for (auto& r : working_array)
		r.resize(columns);

	size_t intRowCount {}, intColumnCount {};
	ifstream input_file1(import_file);

	for (string working_string; getline(input_file1, working_string); )
		if (!working_string.empty())
			working_array[intRowCount++][intColumnCount] = working_string;

	// send the working_array ptr to the class function and perform it's function
	raw_file.SpreadArray(working_array);
}

Looks to me you need to make a dynamic 2D array at compile time. Although there are multiple ways to do this - the modern (and recommended) approach would be to use vectors in the STL library, here, I will give an example of the old fashioned way using an arrays of pointers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

#include <iostream>


void func (std::string** ptr, int sizeRow, int sizeColumn) {

    for (int i = 0; i < sizeRow; i++) {

        for (int j = 0; j < sizeColumn; j++) {

            std::cout << *(*(ptr + i) + j) << std::endl;
        }
    }
}


int main()
{ 

    int sizeRow, sizeColumn;
    std::cout << "Enter N Rows: " << std::endl;
    std::cin >> sizeRow;
    std::cout << "Enter N Columns: " << std::endl;
    std::cin >> sizeColumn;

    //dynamically assign memory using a pointer that points to an array of std::string pointers

    std::string** ptr = new std::string* [sizeRow];
    for (int i = 0; i < sizeColumn; i++)
        ptr[i] = new std::string[sizeColumn];

    //get some input from the user

    for (int i = 0; i < sizeRow; i++) {

        for (int j = 0; j < sizeColumn; j++) {

            std::cout << "Enter string: " << std::endl;
            std::cin >> ptr[i][j];
        }
    }

    //pass prt to function for print out

    func(ptr, sizeRow, sizeColumn);
    
    return 0;
}



Bare in mind you will need to pass array size parameters if you are passing by pointer.
Last edited on
First this line:

string working_array[rows][columns] = {};
Should fail to compile since rows and columns are not compile time constants. C++ doesn't allow VLA.

Second working_array is not a pointer it is a multidimensional array.

I would recommend that you use a std::vector instead of the arrays.


Thank you for the response.

So if I have a defined array intArray[10][5], I can't pass just intArray and it will be a memory address to the double array? The book I am reading "Object Oriented Programming in C++ by Robert Lafore (third edition)" states otherwise. Or is this only true with single arrays?

Just trying to set my brain correctly. Also it compiles fine and I can reference on g++ without any warnings when i run g++ -Wall <filein> <fileout>
The code can be somewhat simplified. Also there are some issue. Consider as C++17 (compiles OK but not tested):


Thank you. I figured there was room for simplification and I am not familiar on what C++ version I have to compile over or how to even check or why I would even care (as long as it compiles).

I will look over and compare details. I certainly appreciate the time you took in replying.
I have noticed a few times where people will define as follows (two *'s). Can someone inform me of when and why to use dual ** as opposed to just *?

In reference to examples above:
void func (std::string** ptr, int sizeRow, int sizeColumn) {

I will look into the vector method as I see it mentioned online quite a bit.

Your time in responding is appreciated.
Scott
So if I have a defined array intArray[10][5], I can't pass just intArray and it will be a memory address to the double array?


That is correct. If you want to pass a complete multidimensional to a function the function needs to know that you will be passing an array to the function. For example:
1
2
3
4
5
6
7
8
9
void someFunction(std::string theArray[][5], size_t x, size_t y)
{

}

// In the calling function:

    std::string theArray[10][5];
    someFunction(theArray);


Remember that in C++ array sizes must be compile time constants unless you dynamically allocate the memory.

If you use the horrible dynamically allocated arrays then you would pass the array by pointer:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void someFunction(std::string **theArray, size_t columns, size_t rows)
{

}

...
   // In calling function:
    string **working_array = new string*[columns];
    for(size_t i = 0; i < columns; ++i)
        working_array[i] = new string[rows];
...
    raw_file.SpreadArray(working_array, columns, rows);

...
    // Don't forget to delete the memory when you're finished with it.  


Note that I am passing the array sizes into the function because once the array decays to a pointer you no longer know the sizes of the arrays.

But I still recommend using std::vector since the vector always knows it's size and the vector also properly takes care of the memory required for the vector.

I will use std::vector, but I just want to know how it works on the back end as I'd like to believe I have a very strong compiler logic. (from my assembler days a long long time ago)

You say
Remember that in C++ array sizes must be compile time constants unless you dynamically allocate the memory.
. I don't want to duplicate the array in memory by making a "new" memory function array, I want to pass the pointer to the double array to the function and manipulate the "live" array via those pointers(no return value needed as I am changing the values of the memory addresses in the array). The array being passed in already defined and allocated memory wise. Technically I could just do it all in the int main() but I wanted to make it more OO instead of relying on procedural programming techniques.

It sounds like I can do what I want in a single dimensional array, but referencing double arrays via ptr memory addresses (and only that one) isn't supported in multi dimensional arrays? (unless I use vector? because it's much easier to manage it sounds like).

Just trying to grasp it. I was all hyped up about manipulating arrays via memory address (aka pointers).
Last edited on
I don't want to duplicate the array in memory by making a "new" memory function array

What? You seem to want to create an array with runtime sizes, so since C++ expressly forbids using runtime variables to size an array, except by using dynamic memory, how do you propose to allocate the memory for the array.

I want to pass the pointer to the double array to the function and manipulate the "live" array via those pointers

Yea but a double pointer is not a multidimensional array, there are subtle differences between arrays and pointers, one of witch is the passing conventions. If you truly want to use multidimensional arrays as pointers then you need to dynamically allocate the memory.

I was all hyped up about manipulating arrays via memory address (aka pointers).

Why? You really don't get many, if any benefits.

As I said you should really consider using one of the standard containers instead of the arrays. The standard containers are much safer and you don't need to mess around with dynamic memory.

Oh and by the way are you really sure that you really need a multidimensional array in the first place? What are you really trying to do?
There is a lot to talk about here.

Basically a std::vector has 2 things: a size and a pointer to the memory which was allocated with new, so the data is on the heap. The actual details are a bit complicated, and requires knowledge of templates and Template Meta Programming (TMP), and exceptions.

The whole idea of the Standard Template Library (STL) is to make life easier for coders. The least of it is not having to do manual memory management, and being able to dynamically resize containers with little effort nor knowledge of what the compiler does implicitly. The STL has RAII, move semantics, and has a lot things which make it behave very well.

One of the problems with people new to C++, is that they often think in C not C++. It's an easy trap to fall into, not meant to be a criticism :+) This a big problem problem because people approach things at too low a level, such as wanting to do things with pointers, as opposed to all the good stuff that comes with the STL. There is a school of thought that says: to teach C++, don't teach them C !

C++ is a multi paradigm language: one can do C style programming; use the STL; do OOP with classes and virtual polymorphism etc; and do template programming and TMP. But doing C style coding when the STL is available is discouraged.

It is possible to do multiple dimension arrays with pointers, it's just the same as in C. But that is tricky to get right, so use an STL container.

Also, using std::vector<std::vector<TYpe>> gives a ragged array because the inner vector size can vary. There is also std::array<std::array<Type>> , but one must specify the size at compile time. Also could use std::vector<std::array<Type>> if that suits you better.

Not sure if you have seen these:

https://stroustrup.com/bs_faq2.html
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines

There was also an article by Stroustrup about the details of RAII, but I couldn't find it.
Also, when using g++, consider this as a minimum with debug info:

g++ -std=c++20 -Wall -Wextra -g cpp.* -o exefile

Apparently now g++ defaults to c++17 standard if none is specified. One should also have a different compilation command to do a release version. Also consider using some sort of make program - make or cmake for example.

There are a zillion options in the g++ documentation, despite that, it's worth having a read of them. Some of them which are not included with Wall and Wextra are useful - related to the use of switch defaults and enums. One can get warnings when there is no default case; if not all the enum values appear in the switch cases.
Apparently now g++ defaults to c++17 standard if none is specified.

Actually this depends on which version of the compiler is being used. And remember by default g++ always defaults to "-std=gnu++XX" not "-std=c++XX".




@jlb

That's a good point :+) I guess I am always reading the docs from the latest version.

At least for newbies, getting gnu++17 is not a bad thing if they didn't specify a standard.
Topic archived. No new replies allowed.