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?