6
\$\begingroup\$

I can't use std::to_string and std::to_wstring function, so I have written a converter:

///@struct ParseInt
///Contains function that parses integer.
template<typename Char_T> struct ParseInt{
    static std::basic_string<Char_T> toString(int x);
};

///Converts integer to std::string
///@param x the integer to convert
///@return std::string from the integer
template<> inline std::string ParseInt<char>::toString(int x){
    int length = 2; //most ints coming here are 2 digits long.
    char* buf = 0;
    do{
        delete[] buf;
        buf = new char[++length + 1];
    }while(sprintf(buf, "%d", x) < 0);
    std::string str(buf);
    delete[] buf;
    return str;
}


///Converts integer to std::wstring
///@param x the integer to convert
///@return std::wstring from the integer
template<> inline std::wstring ParseInt<wchar_t>::toString(int x){
    int length = 2; //most ints coming here are 2 digits long.
    wchar_t* buf = 0;
    do{
        delete[] buf;
        buf = new wchar_t[++length + 1];
    }while(swprintf(buf, length + 1, L"%d", x) < 0);
    std::wstring str(buf);
    delete[] buf;
    return str;
}

I am not sure how efficient it is. Is there any solution that is more efficient?

\$\endgroup\$
5
  • 1
    \$\begingroup\$ For misunderstanding your original question I can give you at least +1 ;-) \$\endgroup\$
    – t3chb0t
    Commented Nov 15, 2016 at 9:26
  • \$\begingroup\$ There are a couple of problems: your buffer sizes are sizeof(int) * CHAR_BIT (which is incorrect, as it doesn't correlate like this, to the max number of digits in an integer. Still, the solution will work. also, be careful about swallowing errors on failure: This version will keep working and simply provide invalid results to client code (I don't know your use case - maybe this is what you need, but it makes me wary :) ). \$\endgroup\$
    – utnapistim
    Commented Nov 15, 2016 at 13:42
  • \$\begingroup\$ @utnapistim : it is incorrect but it will always be big enough, right? The max CHAR_BIT I will be working on is 32-bit \$\endgroup\$
    – Jahid
    Commented Nov 15, 2016 at 14:11
  • \$\begingroup\$ It should be fine. \$\endgroup\$
    – utnapistim
    Commented Nov 15, 2016 at 14:40
  • \$\begingroup\$ Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. \$\endgroup\$ Commented Nov 15, 2016 at 15:54

3 Answers 3

3
\$\begingroup\$

I am not sure how efficient it is. Is there any solution that is more efficient?

Very inefficient.

First, you allocate buffers with new/delete. You should not do that. Second, you allocate in a loop. You shouldn't do that either.

Consider this code instead (function renamed to to_string, as what you do is not parsing - parsing would be the inverse operation - taking a string and extracting an integer from it):

auto print(char * buffer, int x)
{
    return sprintf(buffer, "%d", x);     
}

auto print(wchar_t * buffer, int x)
{
    return wsprintf(buffer, "%d", x);     
}

///Converts integer to std::string
///@param x the integer to convert
///@return std::string/std::wstring from the integer
template<typename C,
    typename T=std::char_traits<C>,
    typename A=std::allocator<C>>
std::basic_string<C,T,A> to_string(int x)
{
    C buffer[ 64 ] = { 0 }; // no new/delete required
                            // no loop required
                            // max int on 32bit architectures is
                            // 2,147,483,647; 64 bytes is "big enough"

    auto written = print(buffer, "%d", x);
    assert(written > 0); // should always be true
    return std::basic_string<C,T,A>{ buffer, buffer + written };
}

Advantages:

  • there is no code repetition (easier to maintain)
  • efficiency is linear in to_string (no loops)
  • code is simpler and more straight-forward
\$\endgroup\$
6
  • \$\begingroup\$ Unix like systems don't have wsprintf \$\endgroup\$
    – Jahid
    Commented Nov 15, 2016 at 12:30
  • \$\begingroup\$ So, basically what you are saying that I should allocate the largest space possible and do it in one go..., is that right? \$\endgroup\$
    – Jahid
    Commented Nov 15, 2016 at 12:38
  • \$\begingroup\$ @Jahid, you are correct. Apparently, on Linux you would have to use swprintf. \$\endgroup\$
    – utnapistim
    Commented Nov 15, 2016 at 13:18
  • \$\begingroup\$ ... also, yes: you should do it in one go. \$\endgroup\$
    – utnapistim
    Commented Nov 15, 2016 at 13:19
  • \$\begingroup\$ check out my new code, is it close enough? unfortunately the repetion can't be avoided, I have other versions (for u16string and u32string) that need template specializations.. \$\endgroup\$
    – Jahid
    Commented Nov 15, 2016 at 13:23
1
\$\begingroup\$

It seems to me that if you really have to do this, you might want to consider using snprintf. This lets you specify the size of buffer you're supplying and if the buffer you pass is too small, it tells you the size you do need.

// compute the length (including space for NUL terminator)
size_t length = snprintf(NULL, 0, "%d", x) + 1;

// Allocate a string of that length:
std::string s(length);

// do the conversion:
snprintf(&s[0], length, "%d", x) + 1;

Unfortunately, there is no matching snwprintf, so a wide character version of this may be somewhat more challenging. One possibility is to compute the length by writing the data to a temporary file, the delete the file and use only the return value. This is fairly simple (much like the code above), but likely to be quite slow in most cases (though I've tried to benchmark it to see if it's faster or slower than repeatedly allocating bigger chunks of memory until swprintf succeeds).

\$\endgroup\$
2
  • \$\begingroup\$ snprintf is C++11, is it not? \$\endgroup\$
    – Jahid
    Commented Nov 15, 2016 at 19:54
  • \$\begingroup\$ C++11 and C99, so most compilers have had it for a long time. \$\endgroup\$ Commented Nov 15, 2016 at 19:57
0
\$\begingroup\$

Now I have written it so that it can convert int to std::string, std::wstring (and to std::u16string, std::u32string when >=C++11 is being used):

#include <string>       // std::string, std::wstring
#include <cstdio>       // std::sprintf
#include <cwchar>       // std::mbstate_t, std::swprintf
#include <climits>      // CHAR_BIT
#include <cassert>      // assert()
#if __cplusplus >= 201103L
    //These will be included if >=C++11 is used
    #include <codecvt>
    #include <locale>  // std::wstring_convert
#endif

template<bool B, typename T = void>
struct EnableIf{};
template<typename T>
struct EnableIf<true, T>{typedef T Type;};

template<typename T1, typename T2>
struct IsSame{ static const bool value = false; };
template<typename T>
struct IsSame<T,T>{ static const bool value = true; };



#if __cplusplus >= 201103L

///Convenience wrapper of std::codecvt (`>=C++11`)
template <class internT, class externT, class stateT>
struct Codecvt : std::codecvt<internT,externT,stateT>
{ ~Codecvt(){} };

///@struct ConvUTF
///`UTF-8 <> UTF-16` and `UTF-8 <> UTF32` converter (`>=C++11`).
template<typename Char_T>
struct ConvUTF { typedef std::wstring_convert<Codecvt<Char_T, char, std::mbstate_t>, Char_T> Converter; };
#endif

///@struct ConvInt
///Contains a function to convert integer to string (either `std::string`
///or `std::wstring` ( or `std::u16string` or `std::u32string` for >=C++11)
///@tparam Char_T Basic character type (`char`, `wchar`, `char16_t`, `char32_t`).
template<typename Char_T, typename T = Char_T> struct ConvInt{};

template<typename Char_T> struct ConvInt<Char_T, typename EnableIf<
IsSame<Char_T, char>::value|IsSame<Char_T, wchar_t>::value, Char_T>::Type>{
    //wrapper of sprintf or swprintf
    static int mysprint(Char_T*, size_t size, int x);

    ///Converts an integer to std::string/std::wstring
    ///@param x the integer to convert
    ///@return std::string/std::wstring from the integer
    static std::basic_string<Char_T> toString(int x){
        Char_T buf[sizeof(int)*CHAR_BIT]; //sizeof(int)*CHAR_BIT should always be sufficient
        int written = mysprint(buf, sizeof(buf), x);
        assert(written > 0);
        return std::basic_string<Char_T>(buf);
    }
};

template<> inline int ConvInt<char>::mysprint(char* buf, size_t size, int x){
    return sprintf(buf, "%d", x);
}
template<> inline int ConvInt<wchar_t>::mysprint(wchar_t* buf, size_t size, int x){
    return swprintf(buf, size, L"%d", x);
}


#if __cplusplus >= 201103L

template<typename Char_T> struct ConvInt<Char_T, typename EnableIf<
IsSame<Char_T, char16_t>::value|IsSame<Char_T, char32_t>::value, Char_T>::Type>{

    ///Converts integer to std::u16string/std::u32string.
    ///Uses codecvt for conversion.
    ///@param x int to convert
    ///@return std::u16string/std::u32string from the integer
    static std::basic_string<Char_T> toString(int x){
        std::string s = std::to_string(x);
        typename ConvUTF<Char_T>::Converter conv;
        return conv.from_bytes(s);
    }
};
#endif

Usage example:

int main(){

    std::cout<<"\nstring: "<<ConvInt<char>::toString(-48);
    std::wcout<<"\nwstring: "<<ConvInt<wchar_t>::toString(-2147483647);
    #if __cplusplus >= 201103L
    ConvUTF<char16_t>::Converter conv;
    ConvUTF<char32_t>::Converter conv2;

    std::cout<<"\nu16string: "<<conv.to_bytes(ConvInt<char16_t>::toString(-48));
    std::cout<<"\nu32string: "<<conv2.to_bytes(ConvInt<char32_t>::toString(-48));
    #endif
    return 0;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.