views:

129

answers:

6

I am passing an array to a function, and i am initializing it globally with some values. I am using empty string in end of array to determine the array length.

Now, Is there some way to automatically initialize the array to have extra empty item in the end of it, so i have no chances to forget it from there? Just like the char[] works, it adds extra null to the end IIRC.

Here is my code what im using now:

struct twostrings {
    string s1, s2;
};

twostrings options[] = {
    {"text1", "more text1"},
    {"text2", "more text2"},
    {"text3", "more text3"},
    {""}, // tells that the array ends here
}

int get_len(twostrings opt[]){
    int p = 0;
    while(1){
        if(opt[p].s1 == ""){
            return p;
        }
        p++;
        // now here is a possibility to go in infinite loop if i forgot the empty string.
        // currently i have a code here that checks if p > 10000 and gives error message to me if i manage to forget that empty string in accident.
    }
    return p;
}

void dosomething(twostrings options[]){
    int len = get_len(options);
    for(int p = 0; p < len; p++){
        // do stuff
    }
}

int main(){ // yes its not valid written main function. dont bother about it.
    dosomething(options);
}
+2  A: 

Unforunately, you're not correct. The char array does not end automatically in a null, this is only a side effect of assigning it with a string literal (which has the automatic null at the end).

char x[] = "ABC"; // size 4, contains A, B, C, \0.
char x[] = {'A','B','C'}; // size 3, contains no terminating null.

So the short answer is no, there's no way to automatically end arrays with an automatic entry. There are a bunch of other options though, such as STL vectors which have other means of determining when you've reached the end. In C++0x there'll probably (IIRC) be a way to initialize the vector just like you'd like.

HTH.

EDIT:
Personally, I prefer to add the extra 0 at the end of the array myself, but I suppose there are ways to work around it using macros.

#define ARRAY(...) {__VA_ARGS__, {0}}

and use it like so

struct foo { char* x; char* y; }

struct foo x[] = ARRAY({"abc", "xyz"}, {"def","uvw"});

I have no idea if this works (and I have no preprocessor handy), and as I said, personally I don't like it. It also requires the first element in the struct to be something which can be assigned 0 to mark the end of the array.

Of course, this forces you to remember to wrap it in the macro call, which is pretty much as bad as forcing you to remember to terminate the array.

EDIT:
I just had a chance to test this and it works. Turns out variadic macros are, so far anyway, C only. However some (most?) C++ compilers support them anyway, a quick search turned up g++ and visual studio. Still I wouldn't favor this approach, I just added it for completeness.

roe
variadic macros don't exist in C++.
Alexandre C.
really? Interesting, sorry about that. It'd be interesting to know if the scheme works in C though, anyone's got a preprocessor handy?
roe
@roe: http://codepad.org/, but yes, it does work – I use it in a few places (even with C++, since my target compilers support it); however, with braces in particular, you have to sometimes be aware of how it's parsed: `MACRO({a,b})` is *two* parameters.
Roger Pate
@Roger: thanks, (and thanks for the link too, didn't cross my mind). The fact that it's two parameters is only relevant when not using varargs (the are concatenated anyway), but thanks for pointing it out.
roe
+5  A: 

Passing around C arrays is not very idiomatic in C++. Try using a std::vector instead:

#include <vector>
#include <string>

struct twostrings {
  std::string s1, s2;
};

typedef std::vector<twostrings> option_type;

twostrings options[] = {
    {"text1", "more text1"},
    {"text2", "more text2"},
    {"text3", "more text3"}
};

int get_len(const option_type& options){
  return options.size();
}

void dosomething(const option_type& options){
    int len = get_len(options);
    for(int p = 0; p < len; p++){
        // do stuff
    }
}


int main() {  // This main function is perfectly fine!
    option_type opt_vector(options, options + (sizeof options / sizeof options[0]));
    dosomething(opt_vector);
}
Philipp
i dont see why is this got 5 votes up, this is really horrible way; i need to repeat code and separately call a vector convertor function for each array. lets guess i have 5000 arrays....
Newbie
Tell us what you actually want, then somebody will find a better design.
Philipp
+1  A: 

Pass the length or the end instead of using a sentinel:

template<class T, int N>
int len(T (&)[N]) { // exists in a more general form as boost::size
  return N;
}

typedef std::pair<std::string, std::string> twostrings;
// std::pairs have first and second members of the given types

void dosomething(twostrings options[], int size);
// call as: dosomething(array, len(array));

# or:

template<class T, int N>
T* end(T (&a)[N]) { // exists in a more general form as boost::end
  return a + N;
}

void dosomething(twostrings* options_begin, twooptions* options_end);
// call as: dosomething(array, end(array));

// usage example:
void dosomething(twostrings* options_begin, twooptions* options_end) {
  // you might name the parameters just 'begin' and 'end'
  for (; options_begin != options_end; ++options_begin) {
    // the 'begin' var advances through the entire sequence
    // use for (twostrings* current = options_begin; current != options_end; ++current)
    // if a separate copy is required
    cout << options_begin->first << ": " << options_begin->second << '\n';
  }
}

Note the [begin, end) iterator pattern (that's inclusive begin, exclusive end) is common in the stdlib (e.g. look at std::sort from <algorithm>).

This is a good halfway measure between arrays and containers such as std::vector, and allows you to keep the easy initialization syntax you have now (C++0x gives you that same syntax with containers such as std::vector, but 0x is not quite yet ready).

Roger Pate
I dont like the fact i have to manually tell how long something is, feels really dirty. I was hoping this could be automatized somehow inside a struct, as how string datatype works, doesnt it store the length in the struct automatically? and when you call .length() it just copies the value from memory?
Newbie
@Newbie: That would be using a container such as std::vector, which has a size method just like std::string does. The length of arrays isn't stored anywhere, but it is available at compile-time as part of the type, and the *len* and *end* functions above exploit that fact.
Roger Pate
`len` function in the snippet above returns length of statically allocated arrays, so you don't need to specify it explicitly.
n0rd
A: 

There are better ways of finding array lengths. You can use:

 1. sizeof(options) / sizeof(twostrings);

 2. sizeof(options) / sizeof(options[0]);

 3. std::vector<twostrings> options;
    options.size();

 4. ARRAYSIZE(options); (windows only)
DanDan
i tried sizeof before, but it didnt work inside the function
Newbie
Should do, it's a C++ keyword.
DanDan
sizeof(X) / sizeof(X[0]) must be applied where the actual definition is known. The method argument is basically jsut a pointer, with the length not known anymore.
peterchen
Not just basically, a function parameter of type "T[]" is [completely a pointer](http://stackoverflow.com/questions/2368750/c-function-declaration/2368914#2368914).
Roger Pate
A: 

Don't use C style arrays in C++, they're just not worth the effort compared to vector.size(). You should use a boost::array<twostrings, length> for a static array.

Hell, you should probably just not use a static value.

DeadMG
A: 

Btw, if(opt[p].s1 == "") is checking 2 const char * pointers for equality, not 2 strings. Although compiller usualy optimizes equal string constants to point to one place, it is still an error.

You should use a NULL sentinell as it was adviced by Svisstack earlier.

edit: Proof

#include <stdio.h>

const char *one = "the string";
void main(){
    const char *other = "the string";
    printf("adress of 'one' = %x, it contains \"%s\"\n", one, one);
    printf("adress of 'other' = %x, it contains \"%s\"\n", other, other);
    if(one == other){
        printf("one == other\n", one);
    } else {
        printf("one != other\n", one);
    }
}

Output:

k:\temp>cl test.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
/out:test.exe
test.obj

k:\temp>test.exe
adress of 'one' = 3d8140, it contains "the string"
adress of 'other' = 3d814c, it contains "the string"
one != other
Dark
i dont see how that is an error, i could as well write there "lol" and compare if there is "lol" it means it ends.
Newbie
Are you sure of this? Isn't the string == operator overloaded to do string comparisons?
roe
std::string do have overloads. char* 'string' does not.added sample code to the post.
Dark
i tried it now... it says error when i put NULL there. just tell me the code for array and for check function, because i cant get it working
Newbie