简体   繁体   中英

How to format wide char string via vswprintf on OSX (want to return std::wstring)

I need implement a function to format wide char string and return std::wstring. My implementation is:

std::wstring format(const wchar_t* fmt, ...)
{
    std::wstring ret;

    va_list va;
    va_start(va, fmt);

    int size = vswprintf(nullptr, 0, fmt, va);
    if (size > 0)
    {
        ret.resize(size + 1);
        vswprintf(&ret[0], size + 1, fmt, va);
    }

    va_end(va);
    return ret;
}

it works well on windows, but unfortunately it doesn't work on osx because vswprintf(nullptr, 0, fmt, va) will return -1.

only std can be used and I can't assume the string length is in a certain range.

I think there should be some way to do that, but I can't find, can you help? thanks

It appears that the Darwin version is actually behaving according to the specification for vfwprintf . Unfortunately, it also seems there is no convenient way to calculate the length of a wide formatted string in the same way you would a regular single byte string (by passing a NULL destination string).

After much researching, I have found there seem to be two viable solutions, both of which are mentioned in this answer:

I dislike the iterative allocation approach as it seems inefficient to blindly attempt the same operation many times until you have allocated enough.

My preferred solution is to format the string (once!) to /dev/null first in order to calculate the length, and then use this to size your buffer. It is necessary to copy the variable args list to do this, as it is consumed by the vs*functions. This is closest to how you were doing it anyway, just with a null stream instead of a null string.

My proposed solution is thus:

std::wstring format(const wchar_t* fmt, ...)
{
    std::wstring ret;

    va_list va;
    va_start(va, fmt);

    va_list vc;
    va_copy(vc, va);

    FILE* fp = fopen("/dev/null", "w");
    int size = vfwprintf(fp, fmt, vc);
    fclose(fp);

    if (size > 0)
    {
        ret.resize(size + 1);
        vswprintf(&ret[0], size + 1, fmt, va);
    }

    va_end(va);
    va_end(vc);

    return ret;
}

I tested this on Mac OS X with the following code, and observed the expected output:

int main(int argc, char* argv[])
{
    auto outstr = format(L"Hello %ls, you are %u years old.\n", L"Fred", 65);

    std::wcout << outstr << std::endl;

    return 0;
}

Pseudocode:

buffer.resize(1)
loop:
    res = vswprintf(buffer, args)
    if res indicates success:
        break
    buffer.resize(buffer.size() * 2)
buffer.truncate()

In other words, you simply try with increasing buffer sizes until you succeed. I'd start off with something larger than 1 char though, basically so that most calls don't have to be repeated, but that's just performance finetuning.

One possible solution might be to use the C11 standard function vswprintf_s function. This is not fully portable because C11 makes it optional for this function to be implemented, but I mention it in case your implementation does implement it.

C++14 only specifies the C99 standard library, and this function is in C11. But if your C++ compiler's associated C compiler supports C11 then it is likely to make the function directly available from C++. Even if it doesn't, you could wrap that code in a C function and call that C function from C++.

The vswprintf_s function uses different arguments to vswprintf so check its documentation carefully.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM