Home > database >  What is this std::string constructor doing?
What is this std::string constructor doing?

Time:05-12

I am trying to write a native host client for a WebExtension browser addon in C .

I looked for examples and unfortunately only found a couple ones.

There's this simple good looking one I'm following but I don't understand the part where a string is constructed as follows:

json get_message() {
    char raw_length[4];
    fread(raw_length, 4, sizeof(char), stdin);
    uint32_t message_length = *reinterpret_cast<uint32_t*>(raw_length);
    if(!message_length)
        exit(EXIT_SUCCESS);

    char message[message_length];
    fread(message, message_length, sizeof(char), stdin);
    string m( message, message   sizeof message / sizeof message[0] );
    return json::parse(m);
}

I looked at multiple documentations for C std::string constructors but I can't figure what is going on here exactly.

What does string m( message, message sizeof message / sizeof message[0] ); do?

And why not just do string m(message)?

There is also the char message[message_length]; line which confused me because message_length is not a constant but apparently this is a GCC-specific feature.

CodePudding user response:

What does string m( message, message sizeof message / sizeof message[0] ); do?

message decays to pointer to first element, sizeof message / sizeof message[0] is the number of elements in the array, and message sizeof message / sizeof message[0] is a pointer to the past the end of the array. Note that this is unnecessarily convoluted way to get the number of elements. I would recommend using message message_length instead.

The constructor copies all characters from the range between these iterators (pointers). The iterators span the entire array.

What I don't understand is I can't seem to find a std::string constructor that accepts (char*, char*)

The constructor looks like this (simplified):

template<class InputIt>
string(InputIt first, InputIt last);

The template parameter is deduced to be char* from the arguments.

And why not just do string m(message)?

That constructor requires that message is terminated by null character. If the argument isn't terminated by null character, then the behaviour of the program would be undefined. That would be bad.

Also, if the array does contain null terminator characters, then that constructor would not copy those into the string, while the iterator range constructor does. That would be bad if the intention is to copy the null terminators.

There is also the char message[message_length]; line which confused me because message_length is not a constant

This isn't allowed in C . The program is ill-formed.


Since the example is rather poor quality, here is a version that fixes major issues:

json get_message(std::istream& in)
{
    std::uint32_t message_length;
    // NOTE input is assumed to be in native byte order
    in.read(reinterpret_cast<char*>(&message_length), sizeof message_length);
    if (!in)
        throw std::runtime_error("Invalid message length");
    if (!message_length)
        throw std::runtime_error("No input");

    std::string m('\0', message_length);
    in.read(m.data(), message_length);
    if (!in)
        throw std::runtime_error("Invalid message");
    return json::parse(m);
}

You can adjust if you want to use another error handling instead of exceptions. The demonstrated error handling has room for improvement (unique exception classes etc.).

Most significant deviation from the original example is that the function can read from any input stream; not only standard input. I've chosen to do so not only because it's more general, but also because there's actually no standard portable way to read binary from standard input. See C read binary stdin

  • Related