Justin Thurman
Justin Thurman

Reputation: 21

Initializing a Struct's variable const char* const* with C++

I am trying to solve a coding question that requires the results be returned using a given struct. The struct is defined as:

struct Answer
{
    const char* const* lastNames;
    unsigned numberOfPeople;

}

Where the lastNames is a pointer to last names that are each terminated by a non-alpha char. I can not seem to find any way to convert the vector of strings that I am using to compile all the last names into a variable that I can assign to lastNames. I have tried making a single string with all the last names and assigning it with c_str() like so: Ans->lastName = allNames.c_str(); but this gives me an error. Due to the limitations of the question I am unable to change the struct variable to anything else. How can I assign a string to a const char* const*

Upvotes: 0

Views: 1779

Answers (3)

Kenny Ostrom
Kenny Ostrom

Reputation: 5871

You can't just cast stuff. struct Answer is expecting a char**, so you are going to have to build it and keep it valid as long as the struct Answer is in use. At least they were kind enough to let us know they don't intend to modify it or mess with cleaning up the memory, since it takes "const char * const *".

#include <iostream>
#include <vector>
#include <string>
#include <assert.h>

typedef std::vector<std::string> VectorOfStrings_type;

struct Answer
{
    const char* const* lastNames;
    unsigned numberOfPeople;
};

class AnswerWrapper
{
private:
    // construct and maintain memory so the pointers in the Answer struct will be valid
    char ** lastNames;
    unsigned int numberOfPeople;

public:
    AnswerWrapper(const VectorOfStrings_type &input){
        numberOfPeople = input.size();

        // create the array of pointers
        lastNames = static_cast<char**>(
            malloc(numberOfPeople * sizeof(char*)) 
        );

        // create each string
        for (unsigned int i = 0; i < numberOfPeople; ++i){
            const std::string &name = input[i];

            // allocate space
            lastNames[i] = static_cast<char*>(
                malloc(name.size() + 1)
            );

            // copy string
            strncpy(lastNames[i], name.data(), name.size());

            // add null terminator
            lastNames[i][name.size()] = '\0';
        }
    }

    operator Answer (){
        return Answer{ lastNames, numberOfPeople };
    }

    ~AnswerWrapper(){
        // critcally important, left as an exercise
        assert(0);
    }
};

void SomeFunctionWhichUsesAnswer(Answer a){
    // presumably you have some legacy C code here
    // but here's a quick and easy demo
    for (unsigned int i = 0; i < a.numberOfPeople; ++i)
        std::cout << a.lastNames[i] << std::endl;
}

int main() {
    // Here is your vector of strings
    VectorOfStrings_type myData { "custom formatted data goes here", "and more here", "and again" };

    // You must construct a buffer for the "Answer" type, which must remain in scope
    AnswerWrapper temp{ myData };

    // AnswerWrapper is currently in scope, so inside this function, the pointers will be valid
    SomeFunctionWhichUsesAnswer(temp);
}

Also, I noticed that the strings in Answer are not referred to as null terminated. That is a separate issue you can take care of.

Upvotes: 1

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 154045

The structure being used effectively uses a C-style approach to defining a variable sized array of pointers to char (with const sprinkled over it). You’ll need storage for both the array of char const* as well as the entities pointed to. Here is how you could build it from a std::vector<std::string>:

std::vector<std::string> strings = somehow_compute_the_strings();
std::vector<char const*> array;
for (std::string const& s: strings) {
    array.push_back(s.c_str());
}

Answer answer = { array.data(), array.size() };

Of course, you can’t return answer without the pointer inside pointing to stale data: you’d need to keep the two std::vectors alive. Potentially these two objects could be made members of an object the function is called on. To actually return an object of type Answer without a place to hold on to the std::vectors you could allocate the relevant entities and accept that the result will yield a memory leak unless the caller can clean the result up.

Upvotes: 2

Aganju
Aganju

Reputation: 6405

A const member variable can only be assigned in the constructor.
if you can add to the struct, define a constructor, and use the : lastname(value) syntax; or use the struct Answer myVar{value,number}; initialization, right where you declare your instance.

Another - ugly, dangerous, and frowned upon - alternative is a cast: (char**) lastname = value;, or in C++ syntax reinterpret_cast<char**>(lastname) = value. If someone is teaching you either of those approaches, change the teacher.

Upvotes: 0

Related Questions