Home > Net >  initialize vector of structs of string literals in c
initialize vector of structs of string literals in c

Time:07-11

I have an API:

void func(struct s st)
{
    //do some stuff
}

when struct s define as:

struct s
{
    char* str1;
    char* str2;
};

Now, I want to call func() multiple times with different pairs of string literals.

So I wanted store my structs in iterable container. for example std::vector:

const std::vector <struct s> sts= 
{
    {
        "str1",
        "str2"
    },
    {
        "str3",
        "str4"
    }
    ...
};

for(auto& st : sts)
{
   func(st);
} 

But I get an error: ISO C forbids converting a string constant to ‘char*’. I know the problem is that I try to assign string literal (const char*) to char*, but how can I fix it?

I know I can implement init function (or define), and call it every time. something like:

s init_struct(const char* str1, const char* str2)
{
    char *str1_ = strdup(str1);
    char *str2_ = strdup(str2);

    return {str1_, str2_};
 }

but I want my code simply as possible. also, I can't change func() prototype or struct s declaration.

My question is: what is the fastest and cleanest way to initialize iterable container with the structs?

When I say "fastest" I don't mean to performance (my code doesn't run in real time), but in terms of readability.

CodePudding user response:

In C , the type of a string literal is an array of const char. Hence, you should define your structure to take a const char *:

struct s
{
    const char* str1;
    const char* str2;
};

While it would work to use string, this creates unnecessary copies, since the string literals are not going away. (If you want to use a more C type, at least use string_view rather than string, but given how simple this case is, I think const char * is just fine.)

CodePudding user response:

First thing to recognize, you'll need to copy somewhere. On some architectures, cstring literals are in a different kind of memory than char*, so to be standard compliant, you need this.

Second trick, a std::initializer_list<T> can be initialized from a class that descends from T. Thus, you can add a class (scons) that has the proper constructor (and descends from s). While in general you cannot convert container<scons> to container<s>, you can still initialize a vector<s> as it'll look for std::initializer_list<s>, which can be constructed this way. Easier to tell by code:

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

struct s
{
    char* str1;
    char* str2;
};

// even this could be templated, as arg count can be deduced using structured binding in c  17
struct scons : s
{
    scons(const char* s1, const char* s2)
    : s{strdup(s1), strdup(s2)} {}
};

int main() {
    std::vector<s> v =
    {
        scons{
            "str1",
            "str2"
        },
        {
            "str3",
            "str4"
        },
        {
            "str5",
            "str6"
        }
    };
}

Note that you only need to specify the new ctor for the first element, the rest is auto-deduced.

CodePudding user response:

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
struct s
{
    char* str1;
    char* str2;
    s(){

    }
    s(string s1, string s2){
        str1=new char[s1.size() 1];
        str1[s1.size()]=0;
        for(int i(0);i<s1.size();  i)
            str1[i]=s1[i];
        str2=new char[s2.size() 1];
        str2[s2.size()]=0;
        for(int i(0);i<s2.size();  i)
            str2[i]=s2[i];
    }
};
int main(){
    
    const std::vector <struct s> sts= 
    {
        {
            "str1",
            "str2"
        },
        {
            "str3",
            "str4"
        }
    };
    return 0;
}

Is that ok?

  • Related