views:

928

answers:

10

I need to convert a string to a char * for use in strtok_s and have been unable to figure it out. c_str() converts to a const char *, which is incompatible.

Also, if someone could explain to me why the second strtok_s function (inside the loop) is necessary, it'd be a great help. Why do i need to explicitly advance the token rather than, for example, the while loop it is in, which fetches each line of a file consecutively, implicitly.

while( getline(myFile, line) ) { // Only one line anyway. . . is there a better way?
    char * con = line.c_str();
    token = strtok_s( con, "#", &next_token);
    while ((token != NULL))
    {
        printf( " %s\n", token );
        token = strtok_s( NULL, "#", &next_token);
    }
}

related question.

+4  A: 

As Daniel said, you could go with

strdup(line.c_str());

Which is better then the strcpy I originally proposed since it allocates the necessary space

Gab Royer
strcpy gives depreciation warnings?
Nona Urbiz
`strdup(line.c_str())` would probably be better.
Daniel Pryden
Take care: This won't work out of the box. You have to allocate memory for `con`.
Martin B
It may give deprecation warnings in VC++. It is deprecated by Microsoft for reasons of security (which makes their OS look bad), it is not deprecated from the ISO standard library. The warning message also tells you how to fix it (it suggests two ways).
Clifford
+6  A: 

You can't convert to a char * because that would that would allow you to write to std::string's internal buffer. To avoid making std::string's implementation visible, this isn't allowed.

Instead of strtok, try a more "C++-like" way of tokenizing strings. See this question:

http://stackoverflow.com/questions/53849/how-do-i-tokenize-a-string-in-c

Martin B
it seems hard to believe that this cast is simply impossible.
Nona Urbiz
The cast itself is possible using const_cast, but not at all suggested.
MP24
It *is* impossible. The reason is that in object-oriented programming, objects don't like external clients accessing their internal representation directly. See http://en.wikipedia.org/wiki/Information_hiding
Martin B
Encapsulation is the word!
Gab Royer
Steve Jessop
A: 

I think you can first convert string to const char*, then copy the const char* to a char* buffer for further use.

Benny
+6  A: 

Use strdup() to copy the const char * returned by c_str() into a char * (remember to free() it afterwards)

Note that strdup() and free() are C, not C++, functions and you'd be better off using methods of std::string instead.

The second strtok_s() is needed because otherwise your loop won't terminate (token's value won't change).

iWerner
but why do i need to explicitly advance the token rather than for example, the while loop it is in, which fetches each line of a file consecutively, implicitly?
Nona Urbiz
Look at the code again:The first invocation of strtok() gets the first token from the line from the file. Then the while()'s condition checks if token is NULL. If it's not, the printf() is executed, and the next token is extracted.What confuses you is probably the fact that the variable next_token does not in fact store the next token, but rather the remainder of the line.That's just the way strtok_s() works.
iWerner
`strdup` isn't standard C. It is simply a common extension, but by no means guaranteed to be present.
Evan Teran
+1  A: 

strtok works like this:

First call return string from beginning unril the delimiter or all the string if no delimiter were found:

token = strtok_s(con, "#", &next_token);

Second call using with NULL allow you to continue parsing the same string to find the next delimiter:

token = strtok_s(NULL, "#", &next_token);

If you reach the end of the string next call will return NULL;

Patrice Bernassola
but why do i need to explicitly advance the token rather than for example, the while loop it is in, which fetches each line of a file consecutively, implicitly.
Nona Urbiz
What exactly are you asking here? You have to call strtok repeatedly until you have consumed all the tokens from the data you give it, in this case one line of the file. The while loop checks the result from strtok to ensure this takes place.
Kylotan
A: 

the 2nd strtok call is inside the loop. It advances you token pointer so that you print out tokens one-by-one, until you've printed out all of them, the pointer becomes null and you exit the loop.

To answer the 1st part of your question, as other have suggested, c_str() only gives you the internal buffer pointer - you can't modify that, that's why it's const. If you want to modify it, you need to allocate your own buffer and copy the string's contents into it.

azheglov
+2  A: 

Whenever you have a std::string and what you need is a (modifiable) character array, then std::vector<char> is what you need:

void f(char* buffer, std::size_t buffer_size);

void g(std::string& str)
{
  std::vector<char> buffer(str.begin(),str.end());
  // buffer.push_back('\0');    // use this if you need a zero-terminated string
  f(&buffer[0], buffer.size()); // if you added zero-termination, consider it for the size
  str.assign(buffer.begin(), buffer.end());
}
sbi
A: 

If you really need to access the string's internal buffer here is how: &*string.begin(). Direct access to string's buffer is useful in some cases, here you can see such a case.

Cristian Adam
I wouldn't want to do that. Fiddling with the internals of data structures is dangerous in general.
David Thornley
In theory, it is in this case, since `std::string` doesn't even guarantee that it stores its characters in a contiguous piece of memory (as, for example, `std::vector` does since C++03). In practice, nobody has seen an implementation of the class that does _not_ store its characters contiguously. As onebyone says, in C++1x this will be guaranteed.
sbi
+2  A: 

strtok() is a badly designed function to begin with. Check your documentation to see if you have a better one. BTW, never use strtok() in any sort of threaded environment unless your docs specifically say it's safe, since it stores state in between calls and modifies the string it's called on. I assume strtok_s() is a safer version, but it's not going to be a really safe one.

To convert a std::string into the char *, you can do:

char * temp_line = new char[line.size() + 1];  // +1 char for '\0' terminator
strcpy(temp_line, line.c_str());

and use temp_line. Your installation may have a strdup() function, which will duplicate the above.

The reason you need two calls to strtok_s() is that they do different things. The first one tells strtok_s() what string it needs to work on, and the second one continues with the same string. That's the reason for the NULL argument; it tells strtok_s() to keep going with the original string.

Therefore, you need one call to get the first token, and then one for each subsequent token. They could be combined with something like

char * temp_string_pointer = temp_line;
while ((token = strtok_s( con, "#", &next_token)) != NULL)
{
   temp_string_pointer = NULL;

and so on, since that would call strtok_s() once with the string pointer and after that with NULL. Don't use temp_line for this, since you want to delete[] temp_line; after processing.

You may think this is a lot of fiddling around, but that's what strtok() and relatives usually entail.

David Thornley
I'd up-vote you for saying "`strtok()` is a badly designed function to begin with", but then you go and suggest using naked character buffers instead of some resource-managing object. `:(`
sbi
A: 

You can easily write a conversion routine that will tokenize a string and return a vector of sub-strings:

std::vector<std::string> parse(const std::string& str, const char delimiter)
{
    std::vector<std::string> r;

    if(str.empty())
        return r;

    size_t prev = 0, curr = 0;

    do
    {
        if(std::string::npos == (curr = str.find(delimiter, prev)))
            curr = str.length();

        r.push_back(str.substr(prev, curr - prev));
        prev = curr + 1;
    }
    while(prev < (int)str.length());
    return r;
}
Chad