Remaking std::to_string in C++ with UDLs (User Defined Literals)

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
7
down vote

favorite












I would like some feedback on what I should change with my semi remake of std::to_string with UDLs (user-defined literals).



This is compiled and working on linux with C++11 using g++.



#include <deque> // if you use vectors instead you have to add them to the 
//string in reverse order (no push_front())
#include <string>
#include <iostream> // for testing types
#include <cxxabi.h> // ditto
#include <typeinfo> // also for testing types
char * Demangle(const char* Object)

int status;
char * RealName;

RealName = abi::__cxa_demangle(Object, 0 , 0, &status);
return RealName;

std::string operator"" _str(unsigned long long int num)

std::deque<char> digits;
for(char i = 0; num != 0;num = num / 10)

i = '0'+(num % 10);
digits.push_front(i);

std::string converternum;
for(auto i = 0; i< digits.size();i++)
converternum += digits[i];
return converternum;


char * operator"" _cstr(unsigned long long int num)

std::deque<char> digits;
for(char i = 0; num != 0;num = num / 10)

i = '0'+(num % 10);
digits.push_front(i);

std::string converternum;
for(auto i = 0; i< digits.size();i++)
converternum += digits[i];
char * string = (char *)converternum.c_str();
return string;//converternum.c_str();


int main()

auto cstr = 213_cstr;
auto str = 213_str;
std::cout << Demangle(typeid(str).name()) << std::endl <<
Demangle(typeid(cstr).name()) << std::endl;

return 0;







share|improve this question





















  • I see a few close votes, but I think this question is fine. I hope you get some good answers!
    – Phrancis
    Jul 13 at 0:37






  • 2




    If you are working with literals, why not just type auto cstr = "213";instead of auto cstr = 213_cstr;? You save yourself from having to include this library, the 3 characters typed per use, and having to manage the lifetime of the C string. I'm not really seeing the benefit for either C strings or C++ strings.
    – Snowhawk
    Jul 13 at 7:12











  • @Snowhawk : A decidedly contrived answer to your question: one could use operator ""_str(std::numeric_limits<long long>::digits10) to get a numeric value in string form.
    – Edward
    Jul 13 at 14:57










  • @Edward That doesn't look like valid C++, but if you wanted the simplest way to stringify an integer literal, you could simply do auto operator ""_str(char const* s) return std::strings; - that is, take a char const* parameter rather than unsigned long long.
    – indi
    Jul 13 at 23:51
















up vote
7
down vote

favorite












I would like some feedback on what I should change with my semi remake of std::to_string with UDLs (user-defined literals).



This is compiled and working on linux with C++11 using g++.



#include <deque> // if you use vectors instead you have to add them to the 
//string in reverse order (no push_front())
#include <string>
#include <iostream> // for testing types
#include <cxxabi.h> // ditto
#include <typeinfo> // also for testing types
char * Demangle(const char* Object)

int status;
char * RealName;

RealName = abi::__cxa_demangle(Object, 0 , 0, &status);
return RealName;

std::string operator"" _str(unsigned long long int num)

std::deque<char> digits;
for(char i = 0; num != 0;num = num / 10)

i = '0'+(num % 10);
digits.push_front(i);

std::string converternum;
for(auto i = 0; i< digits.size();i++)
converternum += digits[i];
return converternum;


char * operator"" _cstr(unsigned long long int num)

std::deque<char> digits;
for(char i = 0; num != 0;num = num / 10)

i = '0'+(num % 10);
digits.push_front(i);

std::string converternum;
for(auto i = 0; i< digits.size();i++)
converternum += digits[i];
char * string = (char *)converternum.c_str();
return string;//converternum.c_str();


int main()

auto cstr = 213_cstr;
auto str = 213_str;
std::cout << Demangle(typeid(str).name()) << std::endl <<
Demangle(typeid(cstr).name()) << std::endl;

return 0;







share|improve this question





















  • I see a few close votes, but I think this question is fine. I hope you get some good answers!
    – Phrancis
    Jul 13 at 0:37






  • 2




    If you are working with literals, why not just type auto cstr = "213";instead of auto cstr = 213_cstr;? You save yourself from having to include this library, the 3 characters typed per use, and having to manage the lifetime of the C string. I'm not really seeing the benefit for either C strings or C++ strings.
    – Snowhawk
    Jul 13 at 7:12











  • @Snowhawk : A decidedly contrived answer to your question: one could use operator ""_str(std::numeric_limits<long long>::digits10) to get a numeric value in string form.
    – Edward
    Jul 13 at 14:57










  • @Edward That doesn't look like valid C++, but if you wanted the simplest way to stringify an integer literal, you could simply do auto operator ""_str(char const* s) return std::strings; - that is, take a char const* parameter rather than unsigned long long.
    – indi
    Jul 13 at 23:51












up vote
7
down vote

favorite









up vote
7
down vote

favorite











I would like some feedback on what I should change with my semi remake of std::to_string with UDLs (user-defined literals).



This is compiled and working on linux with C++11 using g++.



#include <deque> // if you use vectors instead you have to add them to the 
//string in reverse order (no push_front())
#include <string>
#include <iostream> // for testing types
#include <cxxabi.h> // ditto
#include <typeinfo> // also for testing types
char * Demangle(const char* Object)

int status;
char * RealName;

RealName = abi::__cxa_demangle(Object, 0 , 0, &status);
return RealName;

std::string operator"" _str(unsigned long long int num)

std::deque<char> digits;
for(char i = 0; num != 0;num = num / 10)

i = '0'+(num % 10);
digits.push_front(i);

std::string converternum;
for(auto i = 0; i< digits.size();i++)
converternum += digits[i];
return converternum;


char * operator"" _cstr(unsigned long long int num)

std::deque<char> digits;
for(char i = 0; num != 0;num = num / 10)

i = '0'+(num % 10);
digits.push_front(i);

std::string converternum;
for(auto i = 0; i< digits.size();i++)
converternum += digits[i];
char * string = (char *)converternum.c_str();
return string;//converternum.c_str();


int main()

auto cstr = 213_cstr;
auto str = 213_str;
std::cout << Demangle(typeid(str).name()) << std::endl <<
Demangle(typeid(cstr).name()) << std::endl;

return 0;







share|improve this question













I would like some feedback on what I should change with my semi remake of std::to_string with UDLs (user-defined literals).



This is compiled and working on linux with C++11 using g++.



#include <deque> // if you use vectors instead you have to add them to the 
//string in reverse order (no push_front())
#include <string>
#include <iostream> // for testing types
#include <cxxabi.h> // ditto
#include <typeinfo> // also for testing types
char * Demangle(const char* Object)

int status;
char * RealName;

RealName = abi::__cxa_demangle(Object, 0 , 0, &status);
return RealName;

std::string operator"" _str(unsigned long long int num)

std::deque<char> digits;
for(char i = 0; num != 0;num = num / 10)

i = '0'+(num % 10);
digits.push_front(i);

std::string converternum;
for(auto i = 0; i< digits.size();i++)
converternum += digits[i];
return converternum;


char * operator"" _cstr(unsigned long long int num)

std::deque<char> digits;
for(char i = 0; num != 0;num = num / 10)

i = '0'+(num % 10);
digits.push_front(i);

std::string converternum;
for(auto i = 0; i< digits.size();i++)
converternum += digits[i];
char * string = (char *)converternum.c_str();
return string;//converternum.c_str();


int main()

auto cstr = 213_cstr;
auto str = 213_str;
std::cout << Demangle(typeid(str).name()) << std::endl <<
Demangle(typeid(cstr).name()) << std::endl;

return 0;









share|improve this question












share|improve this question




share|improve this question








edited Jul 13 at 15:11









Edward

43.9k373200




43.9k373200









asked Jul 13 at 0:12









Meme myself and a very creepy

413




413











  • I see a few close votes, but I think this question is fine. I hope you get some good answers!
    – Phrancis
    Jul 13 at 0:37






  • 2




    If you are working with literals, why not just type auto cstr = "213";instead of auto cstr = 213_cstr;? You save yourself from having to include this library, the 3 characters typed per use, and having to manage the lifetime of the C string. I'm not really seeing the benefit for either C strings or C++ strings.
    – Snowhawk
    Jul 13 at 7:12











  • @Snowhawk : A decidedly contrived answer to your question: one could use operator ""_str(std::numeric_limits<long long>::digits10) to get a numeric value in string form.
    – Edward
    Jul 13 at 14:57










  • @Edward That doesn't look like valid C++, but if you wanted the simplest way to stringify an integer literal, you could simply do auto operator ""_str(char const* s) return std::strings; - that is, take a char const* parameter rather than unsigned long long.
    – indi
    Jul 13 at 23:51
















  • I see a few close votes, but I think this question is fine. I hope you get some good answers!
    – Phrancis
    Jul 13 at 0:37






  • 2




    If you are working with literals, why not just type auto cstr = "213";instead of auto cstr = 213_cstr;? You save yourself from having to include this library, the 3 characters typed per use, and having to manage the lifetime of the C string. I'm not really seeing the benefit for either C strings or C++ strings.
    – Snowhawk
    Jul 13 at 7:12











  • @Snowhawk : A decidedly contrived answer to your question: one could use operator ""_str(std::numeric_limits<long long>::digits10) to get a numeric value in string form.
    – Edward
    Jul 13 at 14:57










  • @Edward That doesn't look like valid C++, but if you wanted the simplest way to stringify an integer literal, you could simply do auto operator ""_str(char const* s) return std::strings; - that is, take a char const* parameter rather than unsigned long long.
    – indi
    Jul 13 at 23:51















I see a few close votes, but I think this question is fine. I hope you get some good answers!
– Phrancis
Jul 13 at 0:37




I see a few close votes, but I think this question is fine. I hope you get some good answers!
– Phrancis
Jul 13 at 0:37




2




2




If you are working with literals, why not just type auto cstr = "213";instead of auto cstr = 213_cstr;? You save yourself from having to include this library, the 3 characters typed per use, and having to manage the lifetime of the C string. I'm not really seeing the benefit for either C strings or C++ strings.
– Snowhawk
Jul 13 at 7:12





If you are working with literals, why not just type auto cstr = "213";instead of auto cstr = 213_cstr;? You save yourself from having to include this library, the 3 characters typed per use, and having to manage the lifetime of the C string. I'm not really seeing the benefit for either C strings or C++ strings.
– Snowhawk
Jul 13 at 7:12













@Snowhawk : A decidedly contrived answer to your question: one could use operator ""_str(std::numeric_limits<long long>::digits10) to get a numeric value in string form.
– Edward
Jul 13 at 14:57




@Snowhawk : A decidedly contrived answer to your question: one could use operator ""_str(std::numeric_limits<long long>::digits10) to get a numeric value in string form.
– Edward
Jul 13 at 14:57












@Edward That doesn't look like valid C++, but if you wanted the simplest way to stringify an integer literal, you could simply do auto operator ""_str(char const* s) return std::strings; - that is, take a char const* parameter rather than unsigned long long.
– indi
Jul 13 at 23:51




@Edward That doesn't look like valid C++, but if you wanted the simplest way to stringify an integer literal, you could simply do auto operator ""_str(char const* s) return std::strings; - that is, take a char const* parameter rather than unsigned long long.
– indi
Jul 13 at 23:51










4 Answers
4






active

oldest

votes

















up vote
6
down vote













I'm ignoring all the testing stuff, and just focusing on the two conversion functions.



std::string operator"" _str(unsigned long long int num)
{
std::deque<char> digits;


There's really no need for the deque. You can push_back() all the digits directly into a std::string, and then use std::reverse().



To make it even better, you could use the max value of an unsigned long long and calculate the maximum number of digits you'll need, then use reserve(). But that probably won't net you any real gains in reality, since you'll likely always be under the small-string optimization size where std::string won't actually allocate any memory... another reason to ditch the deque and stick with strings.



for(char i = 0; num != 0;num = num / 10)


Have you tested your conversion functions with zero? Because if num is zero, this loop won't fire at all, and digits will be empty. So "0"_str will result in "".



This is a pretty weird and confusing loop in general. i looks like it's going to be some kind of loop counter, but it's not. All-in-all, this just looks like a case of trying to be too clever, and outsmarting yourself (due to the zero issue).



This could be expressed much more simply and clearly as something like:



do

digits.push_front('0' + (num % 10));
num /= 10;
while (num != 0);


and it won't have the zero bug. (do loops are less common than while, but the reason to use one here - and this should be documented in a comment - is to guarantee that even if num is zero, the loop will fire at least once.)



snipped section about digit character codes



std::string converternum;


I already mentioned that the deque is unnecessary, but if you're really going to use it, you should take advantage of the fact that you know digits.size(), and use reserve() for this string to prevent unnecessary allocations.



char * operator"" _cstr(unsigned long long int num)


This function is mostly identical to the first one, and thus has the same problems. Again, I would ditch the deque and just work with a string. The digits will be in reverse order, but in this case you can fix that with std::reverse_copy() when you copy them into your output buffer.



char * string = (char *)converternum.c_str();
return string;//converternum.c_str();


Here is where you get into real trouble.



First, never, ever use C-style casts. In this case, you've cast away the constness of c_str()... that's bad. Prior to C++17, if you want a non-const pointer to std::string's data, you have to jump through some hoops - you have to do something like &convertnum[0] or &convertnum.front() - and I agree that's a pain. But casting away constness is a red flag that won't pass most code reviews, because it is extremely dangerous.



Second, you are taking a pointer to convertnum's internal data, and returning that... even though convertnum is being destroyed. In other words, you are returning a pointer to freed memory, which is UB.



How do you solve these problems? Well, the don't answer is: Don't use C-style strings. In other words, don't have a _cstr UDL. It's only going to give you grief (it already has!).



But if you really, really must... one option is to do what @hoffmale suggests, and instead of char*, return std::unique_ptr<char>. That might look something like this:



char* operator"" _cstr(unsigned long long int num)

auto digits = std::string;
do

digits += '0' + (num % 10);
num /= 10;
while (num != 0);

auto p = std::make_unique<char>(digits.size() + 1);

std::reverse_copy(digits.cbegin(), digits.cend(), p.get());
p[digits.size()] = ''; // have to manually add the null terminator

return p;



The problem with that is you're now longer returning a C-string, which makes "_cstr" kind of a lie.



So another option is to have some kind of storage with a lifetime longer than the function that will retain the memory for you. Something like:



char* operator"" _cstr(unsigned long long int num)

// Note: you *can't* use a vector<string>, because string's
// memory may be invalidated on moves, like if the vector
// resizes.
static auto storage = std::vector<std::unique_ptr<char>>;

auto digits = std::string;
do

digits += '0' + (num % 10);
num /= 10;
while (num != 0);

auto p = std::make_unique<char>(digits.size() + 1);

std::reverse_copy(digits.cbegin(), digits.cend(), p.get());
p[digits.size()] = '';

// Get the char*, and then move the unique_ptr into storage
// to let it be managed there.
auto result = p.get();
storage.push_back(std::move(p));

return result;



This will "work", and it's basically what some C libraries do (when they don't simply leak). But with no way to release the memory, it will just grow and grow. You could add a mechanism to release the memory, but you would need a way to be sure it's safe for every string. This solution is possible, but unwieldy.



The last solution - the worst case (other than outright UB) - is to define some sort of contract that client code must follow. In plain English, document the function by saying "you must delete the memory when you're done with it". That requires programmers to behave, and is very easy to break, so it is not recommended. But... if you really want a C-string function, it's an option. A bad one, but an option nonetheless.



Summary



You have one critical error in your code:



  • In ""_cstr(unsigned long long), you return a pointer to the internal memory of a std::string that gets destroyed at the end of the function, meaning you're returning a pointer to invalid memory.

You have a couple of logical errors in your code:



  • Both functions will return an empty string when the number is zero, instead of "0".

You have a few inefficiencies:



  • You use deques because you want to add the numbers in reverse order, and reason that makes sense because neither strings nor vectors have push_front(). But that's all unnecessary. You can simply push_back() directly into your result string, and then use std::reverse(). (Or push_back() into a string buffer, and std::reverse_copy() into your final buffer.)

And here are some tips to keep in mind:



It's really good that you're doing testing, but if you're going to do that, you might as well go all in and use something like Google Test. It's header-only, a single include, and it produces very pretty output. A full battery of tests can give you great confidence in your code. Using Google Test might look something like (fair warning, I don't actually use Google Test that often):



TEST(StrTest, HandlesZero) 
EXPECT_EQ(0_str, "0");



To test that the types are right, rather than trying to demangle the names (which, by the way, it's been a long time since I've used abi::__cxa_demangle(), but I'm pretty sure you're not using it correctly), it's so much easier to just use type traits:



TEST(StrTest, ReturnType) 
EXPECT_TRUE((std::is_same_v<decltype(0_str), std::string>));



Another dirty trick you can use to examine types is deliberately generating errors:



template <typename T>
struct TD; // note, never define TD

TD<decltype(0_str)> t; // will trigger an error


The error generated will print the exact type of whatever is in the angle brackets, on every compiler. This is a hackish way to check types, but it can help when you're experimenting.






share|improve this answer























  • C++ guarantees that digits are contiguous. [lex.charset]/3: "In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous."
    – Jerry Coffin
    Jul 13 at 3:07










  • That's good to know. I'm battle-scarred after EBCDIC and its non-contiguous letters.
    – indi
    Jul 13 at 3:13











  • I certainly can't blame you for that!
    – Jerry Coffin
    Jul 13 at 3:14






  • 1




    For tests of type equality, I prefer to do that at compile-time - e.g. static_assert(std::is_same_v<decltype(0_str), std::string>);
    – Toby Speight
    Jul 13 at 8:26










  • In case it's not obvious - the static_assert can still be in the test method. Example in a recent question of mine: Compute mean and variance, incrementally.
    – Toby Speight
    Jul 13 at 15:02

















up vote
5
down vote














Note: Intermingling actual implementation code and testing code/usage example makes for bad reading.




std::string has a constructor that takes two iterators and constructs its contents from them. This allows _str to be simplified to:



std::string operator"" _str(unsigned long long int num)

std::deque<char> digits;

for(; num != 0; num /= 10)

auto digit = '0' + (num % 10);
digits.push_front(digit);


return std::string std::begin(digits), std::end(digits) ;



Similar applies to _cstr. But beware! Since converternum in _cstr gets destructed once the function returns, the pointer returned by c_str() earlier will dangle. To return a valid char*, a copy has to be made - and in that case, the memory could be managed directly anyways:



char* operator"" _cstr(unsigned long long int num)

std::deque<char> digits;

for(; num != 0; num /= 10)

char digit = char'0'+(num % 10);
digits.push_front(digit);


auto string = new char[digits.size() + 1];

std::copy(std::begin(digits), std::end(digits), string);
string[digits.size()] = '';

return string;






share|improve this answer





















  • Thank you, I'm trying to learn more about the standard lib algorithms for these classes and appreciate your help in doing so
    – Meme myself and a very creepy
    Jul 13 at 1:09










  • @Mememyselfandaverycreepy: Generally, I'd suggest returning a std::unique_ptr<char>, but that isn't a C-style string. If you need C-style strings (e.g. for calling C code), you might want to think about exchanging new char[...] for malloc (C can call free, but it doesn't have access to delete)
    – hoffmale
    Jul 13 at 1:12

















up vote
4
down vote













The other reviews have already covered the most important bits, so I'll just address a few things not already mentioned.



Use your own function



If you have strdup available, the operator""_cstr can become a one-liner:



char * operator"" _cstr(unsigned long long int num) 
return strdup(operator""_str(num).c_str());



If you don't have strdup (it's a standard POSIX, but not standard C++ function), one could easily create one or simply write the few lines of code to create the equivalent. Alternatively, one could use the other operator as a base from which to construct a string, but I like this direction better. In fact, I'd probably not provide a cstr operator at all because of the ugliness it introduces for memory management.



Perform static checking



Instead of using abi::__cxa_demangle() which is, as you know, non-portable, we can do this:



static_assert(std::is_same<decltype(str), std::string>(), 
"str does not match expected type 'std::string'n");
static_assert(std::is_same<decltype(cstr), char *>(),
"cstr does not match expected type 'char *'n");


One could even go crazy and define an ugly macro for this:



#define TYPE_CHECK(str, tipo) 
static_assert(std::is_same<decltype(str), tipo>(),
# str " does not match expected type '" # tipo "'n")


Then use it as:



TYPE_CHECK(str, std::string);
TYPE_CHECK(cstr, const char *);





share|improve this answer






























    up vote
    0
    down vote













    As @indi mentioned you can use (const char *) parameter list. This allows _cstr to be constexpr and simplifies it:



    constexpr const char * operator"" _cstr(const char * cstr) 
    return cstr;



    constexpr here helps you to perform static checking:



    constexpr bool cstring_equal(const char * lhs, const char * rhs) 

    static_assert(cstring_equal(123_cstr, "123"), "123_cstr doesn't match expected value "123"");


    Also (const char *) parameter list allows floating-point: 12.3_cstr.






    share|improve this answer





















      Your Answer




      StackExchange.ifUsing("editor", function ()
      return StackExchange.using("mathjaxEditing", function ()
      StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
      StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
      );
      );
      , "mathjax-editing");

      StackExchange.ifUsing("editor", function ()
      StackExchange.using("externalEditor", function ()
      StackExchange.using("snippets", function ()
      StackExchange.snippets.init();
      );
      );
      , "code-snippets");

      StackExchange.ready(function()
      var channelOptions =
      tags: "".split(" "),
      id: "196"
      ;
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function()
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled)
      StackExchange.using("snippets", function()
      createEditor();
      );

      else
      createEditor();

      );

      function createEditor()
      StackExchange.prepareEditor(
      heartbeatType: 'answer',
      convertImagesToLinks: false,
      noModals: false,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: "",
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      );



      );








       

      draft saved


      draft discarded


















      StackExchange.ready(
      function ()
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f198393%2fremaking-stdto-string-in-c-with-udls-user-defined-literals%23new-answer', 'question_page');

      );

      Post as a guest






























      4 Answers
      4






      active

      oldest

      votes








      4 Answers
      4






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes








      up vote
      6
      down vote













      I'm ignoring all the testing stuff, and just focusing on the two conversion functions.



      std::string operator"" _str(unsigned long long int num)
      {
      std::deque<char> digits;


      There's really no need for the deque. You can push_back() all the digits directly into a std::string, and then use std::reverse().



      To make it even better, you could use the max value of an unsigned long long and calculate the maximum number of digits you'll need, then use reserve(). But that probably won't net you any real gains in reality, since you'll likely always be under the small-string optimization size where std::string won't actually allocate any memory... another reason to ditch the deque and stick with strings.



      for(char i = 0; num != 0;num = num / 10)


      Have you tested your conversion functions with zero? Because if num is zero, this loop won't fire at all, and digits will be empty. So "0"_str will result in "".



      This is a pretty weird and confusing loop in general. i looks like it's going to be some kind of loop counter, but it's not. All-in-all, this just looks like a case of trying to be too clever, and outsmarting yourself (due to the zero issue).



      This could be expressed much more simply and clearly as something like:



      do

      digits.push_front('0' + (num % 10));
      num /= 10;
      while (num != 0);


      and it won't have the zero bug. (do loops are less common than while, but the reason to use one here - and this should be documented in a comment - is to guarantee that even if num is zero, the loop will fire at least once.)



      snipped section about digit character codes



      std::string converternum;


      I already mentioned that the deque is unnecessary, but if you're really going to use it, you should take advantage of the fact that you know digits.size(), and use reserve() for this string to prevent unnecessary allocations.



      char * operator"" _cstr(unsigned long long int num)


      This function is mostly identical to the first one, and thus has the same problems. Again, I would ditch the deque and just work with a string. The digits will be in reverse order, but in this case you can fix that with std::reverse_copy() when you copy them into your output buffer.



      char * string = (char *)converternum.c_str();
      return string;//converternum.c_str();


      Here is where you get into real trouble.



      First, never, ever use C-style casts. In this case, you've cast away the constness of c_str()... that's bad. Prior to C++17, if you want a non-const pointer to std::string's data, you have to jump through some hoops - you have to do something like &convertnum[0] or &convertnum.front() - and I agree that's a pain. But casting away constness is a red flag that won't pass most code reviews, because it is extremely dangerous.



      Second, you are taking a pointer to convertnum's internal data, and returning that... even though convertnum is being destroyed. In other words, you are returning a pointer to freed memory, which is UB.



      How do you solve these problems? Well, the don't answer is: Don't use C-style strings. In other words, don't have a _cstr UDL. It's only going to give you grief (it already has!).



      But if you really, really must... one option is to do what @hoffmale suggests, and instead of char*, return std::unique_ptr<char>. That might look something like this:



      char* operator"" _cstr(unsigned long long int num)

      auto digits = std::string;
      do

      digits += '0' + (num % 10);
      num /= 10;
      while (num != 0);

      auto p = std::make_unique<char>(digits.size() + 1);

      std::reverse_copy(digits.cbegin(), digits.cend(), p.get());
      p[digits.size()] = ''; // have to manually add the null terminator

      return p;



      The problem with that is you're now longer returning a C-string, which makes "_cstr" kind of a lie.



      So another option is to have some kind of storage with a lifetime longer than the function that will retain the memory for you. Something like:



      char* operator"" _cstr(unsigned long long int num)

      // Note: you *can't* use a vector<string>, because string's
      // memory may be invalidated on moves, like if the vector
      // resizes.
      static auto storage = std::vector<std::unique_ptr<char>>;

      auto digits = std::string;
      do

      digits += '0' + (num % 10);
      num /= 10;
      while (num != 0);

      auto p = std::make_unique<char>(digits.size() + 1);

      std::reverse_copy(digits.cbegin(), digits.cend(), p.get());
      p[digits.size()] = '';

      // Get the char*, and then move the unique_ptr into storage
      // to let it be managed there.
      auto result = p.get();
      storage.push_back(std::move(p));

      return result;



      This will "work", and it's basically what some C libraries do (when they don't simply leak). But with no way to release the memory, it will just grow and grow. You could add a mechanism to release the memory, but you would need a way to be sure it's safe for every string. This solution is possible, but unwieldy.



      The last solution - the worst case (other than outright UB) - is to define some sort of contract that client code must follow. In plain English, document the function by saying "you must delete the memory when you're done with it". That requires programmers to behave, and is very easy to break, so it is not recommended. But... if you really want a C-string function, it's an option. A bad one, but an option nonetheless.



      Summary



      You have one critical error in your code:



      • In ""_cstr(unsigned long long), you return a pointer to the internal memory of a std::string that gets destroyed at the end of the function, meaning you're returning a pointer to invalid memory.

      You have a couple of logical errors in your code:



      • Both functions will return an empty string when the number is zero, instead of "0".

      You have a few inefficiencies:



      • You use deques because you want to add the numbers in reverse order, and reason that makes sense because neither strings nor vectors have push_front(). But that's all unnecessary. You can simply push_back() directly into your result string, and then use std::reverse(). (Or push_back() into a string buffer, and std::reverse_copy() into your final buffer.)

      And here are some tips to keep in mind:



      It's really good that you're doing testing, but if you're going to do that, you might as well go all in and use something like Google Test. It's header-only, a single include, and it produces very pretty output. A full battery of tests can give you great confidence in your code. Using Google Test might look something like (fair warning, I don't actually use Google Test that often):



      TEST(StrTest, HandlesZero) 
      EXPECT_EQ(0_str, "0");



      To test that the types are right, rather than trying to demangle the names (which, by the way, it's been a long time since I've used abi::__cxa_demangle(), but I'm pretty sure you're not using it correctly), it's so much easier to just use type traits:



      TEST(StrTest, ReturnType) 
      EXPECT_TRUE((std::is_same_v<decltype(0_str), std::string>));



      Another dirty trick you can use to examine types is deliberately generating errors:



      template <typename T>
      struct TD; // note, never define TD

      TD<decltype(0_str)> t; // will trigger an error


      The error generated will print the exact type of whatever is in the angle brackets, on every compiler. This is a hackish way to check types, but it can help when you're experimenting.






      share|improve this answer























      • C++ guarantees that digits are contiguous. [lex.charset]/3: "In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous."
        – Jerry Coffin
        Jul 13 at 3:07










      • That's good to know. I'm battle-scarred after EBCDIC and its non-contiguous letters.
        – indi
        Jul 13 at 3:13











      • I certainly can't blame you for that!
        – Jerry Coffin
        Jul 13 at 3:14






      • 1




        For tests of type equality, I prefer to do that at compile-time - e.g. static_assert(std::is_same_v<decltype(0_str), std::string>);
        – Toby Speight
        Jul 13 at 8:26










      • In case it's not obvious - the static_assert can still be in the test method. Example in a recent question of mine: Compute mean and variance, incrementally.
        – Toby Speight
        Jul 13 at 15:02














      up vote
      6
      down vote













      I'm ignoring all the testing stuff, and just focusing on the two conversion functions.



      std::string operator"" _str(unsigned long long int num)
      {
      std::deque<char> digits;


      There's really no need for the deque. You can push_back() all the digits directly into a std::string, and then use std::reverse().



      To make it even better, you could use the max value of an unsigned long long and calculate the maximum number of digits you'll need, then use reserve(). But that probably won't net you any real gains in reality, since you'll likely always be under the small-string optimization size where std::string won't actually allocate any memory... another reason to ditch the deque and stick with strings.



      for(char i = 0; num != 0;num = num / 10)


      Have you tested your conversion functions with zero? Because if num is zero, this loop won't fire at all, and digits will be empty. So "0"_str will result in "".



      This is a pretty weird and confusing loop in general. i looks like it's going to be some kind of loop counter, but it's not. All-in-all, this just looks like a case of trying to be too clever, and outsmarting yourself (due to the zero issue).



      This could be expressed much more simply and clearly as something like:



      do

      digits.push_front('0' + (num % 10));
      num /= 10;
      while (num != 0);


      and it won't have the zero bug. (do loops are less common than while, but the reason to use one here - and this should be documented in a comment - is to guarantee that even if num is zero, the loop will fire at least once.)



      snipped section about digit character codes



      std::string converternum;


      I already mentioned that the deque is unnecessary, but if you're really going to use it, you should take advantage of the fact that you know digits.size(), and use reserve() for this string to prevent unnecessary allocations.



      char * operator"" _cstr(unsigned long long int num)


      This function is mostly identical to the first one, and thus has the same problems. Again, I would ditch the deque and just work with a string. The digits will be in reverse order, but in this case you can fix that with std::reverse_copy() when you copy them into your output buffer.



      char * string = (char *)converternum.c_str();
      return string;//converternum.c_str();


      Here is where you get into real trouble.



      First, never, ever use C-style casts. In this case, you've cast away the constness of c_str()... that's bad. Prior to C++17, if you want a non-const pointer to std::string's data, you have to jump through some hoops - you have to do something like &convertnum[0] or &convertnum.front() - and I agree that's a pain. But casting away constness is a red flag that won't pass most code reviews, because it is extremely dangerous.



      Second, you are taking a pointer to convertnum's internal data, and returning that... even though convertnum is being destroyed. In other words, you are returning a pointer to freed memory, which is UB.



      How do you solve these problems? Well, the don't answer is: Don't use C-style strings. In other words, don't have a _cstr UDL. It's only going to give you grief (it already has!).



      But if you really, really must... one option is to do what @hoffmale suggests, and instead of char*, return std::unique_ptr<char>. That might look something like this:



      char* operator"" _cstr(unsigned long long int num)

      auto digits = std::string;
      do

      digits += '0' + (num % 10);
      num /= 10;
      while (num != 0);

      auto p = std::make_unique<char>(digits.size() + 1);

      std::reverse_copy(digits.cbegin(), digits.cend(), p.get());
      p[digits.size()] = ''; // have to manually add the null terminator

      return p;



      The problem with that is you're now longer returning a C-string, which makes "_cstr" kind of a lie.



      So another option is to have some kind of storage with a lifetime longer than the function that will retain the memory for you. Something like:



      char* operator"" _cstr(unsigned long long int num)

      // Note: you *can't* use a vector<string>, because string's
      // memory may be invalidated on moves, like if the vector
      // resizes.
      static auto storage = std::vector<std::unique_ptr<char>>;

      auto digits = std::string;
      do

      digits += '0' + (num % 10);
      num /= 10;
      while (num != 0);

      auto p = std::make_unique<char>(digits.size() + 1);

      std::reverse_copy(digits.cbegin(), digits.cend(), p.get());
      p[digits.size()] = '';

      // Get the char*, and then move the unique_ptr into storage
      // to let it be managed there.
      auto result = p.get();
      storage.push_back(std::move(p));

      return result;



      This will "work", and it's basically what some C libraries do (when they don't simply leak). But with no way to release the memory, it will just grow and grow. You could add a mechanism to release the memory, but you would need a way to be sure it's safe for every string. This solution is possible, but unwieldy.



      The last solution - the worst case (other than outright UB) - is to define some sort of contract that client code must follow. In plain English, document the function by saying "you must delete the memory when you're done with it". That requires programmers to behave, and is very easy to break, so it is not recommended. But... if you really want a C-string function, it's an option. A bad one, but an option nonetheless.



      Summary



      You have one critical error in your code:



      • In ""_cstr(unsigned long long), you return a pointer to the internal memory of a std::string that gets destroyed at the end of the function, meaning you're returning a pointer to invalid memory.

      You have a couple of logical errors in your code:



      • Both functions will return an empty string when the number is zero, instead of "0".

      You have a few inefficiencies:



      • You use deques because you want to add the numbers in reverse order, and reason that makes sense because neither strings nor vectors have push_front(). But that's all unnecessary. You can simply push_back() directly into your result string, and then use std::reverse(). (Or push_back() into a string buffer, and std::reverse_copy() into your final buffer.)

      And here are some tips to keep in mind:



      It's really good that you're doing testing, but if you're going to do that, you might as well go all in and use something like Google Test. It's header-only, a single include, and it produces very pretty output. A full battery of tests can give you great confidence in your code. Using Google Test might look something like (fair warning, I don't actually use Google Test that often):



      TEST(StrTest, HandlesZero) 
      EXPECT_EQ(0_str, "0");



      To test that the types are right, rather than trying to demangle the names (which, by the way, it's been a long time since I've used abi::__cxa_demangle(), but I'm pretty sure you're not using it correctly), it's so much easier to just use type traits:



      TEST(StrTest, ReturnType) 
      EXPECT_TRUE((std::is_same_v<decltype(0_str), std::string>));



      Another dirty trick you can use to examine types is deliberately generating errors:



      template <typename T>
      struct TD; // note, never define TD

      TD<decltype(0_str)> t; // will trigger an error


      The error generated will print the exact type of whatever is in the angle brackets, on every compiler. This is a hackish way to check types, but it can help when you're experimenting.






      share|improve this answer























      • C++ guarantees that digits are contiguous. [lex.charset]/3: "In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous."
        – Jerry Coffin
        Jul 13 at 3:07










      • That's good to know. I'm battle-scarred after EBCDIC and its non-contiguous letters.
        – indi
        Jul 13 at 3:13











      • I certainly can't blame you for that!
        – Jerry Coffin
        Jul 13 at 3:14






      • 1




        For tests of type equality, I prefer to do that at compile-time - e.g. static_assert(std::is_same_v<decltype(0_str), std::string>);
        – Toby Speight
        Jul 13 at 8:26










      • In case it's not obvious - the static_assert can still be in the test method. Example in a recent question of mine: Compute mean and variance, incrementally.
        – Toby Speight
        Jul 13 at 15:02












      up vote
      6
      down vote










      up vote
      6
      down vote









      I'm ignoring all the testing stuff, and just focusing on the two conversion functions.



      std::string operator"" _str(unsigned long long int num)
      {
      std::deque<char> digits;


      There's really no need for the deque. You can push_back() all the digits directly into a std::string, and then use std::reverse().



      To make it even better, you could use the max value of an unsigned long long and calculate the maximum number of digits you'll need, then use reserve(). But that probably won't net you any real gains in reality, since you'll likely always be under the small-string optimization size where std::string won't actually allocate any memory... another reason to ditch the deque and stick with strings.



      for(char i = 0; num != 0;num = num / 10)


      Have you tested your conversion functions with zero? Because if num is zero, this loop won't fire at all, and digits will be empty. So "0"_str will result in "".



      This is a pretty weird and confusing loop in general. i looks like it's going to be some kind of loop counter, but it's not. All-in-all, this just looks like a case of trying to be too clever, and outsmarting yourself (due to the zero issue).



      This could be expressed much more simply and clearly as something like:



      do

      digits.push_front('0' + (num % 10));
      num /= 10;
      while (num != 0);


      and it won't have the zero bug. (do loops are less common than while, but the reason to use one here - and this should be documented in a comment - is to guarantee that even if num is zero, the loop will fire at least once.)



      snipped section about digit character codes



      std::string converternum;


      I already mentioned that the deque is unnecessary, but if you're really going to use it, you should take advantage of the fact that you know digits.size(), and use reserve() for this string to prevent unnecessary allocations.



      char * operator"" _cstr(unsigned long long int num)


      This function is mostly identical to the first one, and thus has the same problems. Again, I would ditch the deque and just work with a string. The digits will be in reverse order, but in this case you can fix that with std::reverse_copy() when you copy them into your output buffer.



      char * string = (char *)converternum.c_str();
      return string;//converternum.c_str();


      Here is where you get into real trouble.



      First, never, ever use C-style casts. In this case, you've cast away the constness of c_str()... that's bad. Prior to C++17, if you want a non-const pointer to std::string's data, you have to jump through some hoops - you have to do something like &convertnum[0] or &convertnum.front() - and I agree that's a pain. But casting away constness is a red flag that won't pass most code reviews, because it is extremely dangerous.



      Second, you are taking a pointer to convertnum's internal data, and returning that... even though convertnum is being destroyed. In other words, you are returning a pointer to freed memory, which is UB.



      How do you solve these problems? Well, the don't answer is: Don't use C-style strings. In other words, don't have a _cstr UDL. It's only going to give you grief (it already has!).



      But if you really, really must... one option is to do what @hoffmale suggests, and instead of char*, return std::unique_ptr<char>. That might look something like this:



      char* operator"" _cstr(unsigned long long int num)

      auto digits = std::string;
      do

      digits += '0' + (num % 10);
      num /= 10;
      while (num != 0);

      auto p = std::make_unique<char>(digits.size() + 1);

      std::reverse_copy(digits.cbegin(), digits.cend(), p.get());
      p[digits.size()] = ''; // have to manually add the null terminator

      return p;



      The problem with that is you're now longer returning a C-string, which makes "_cstr" kind of a lie.



      So another option is to have some kind of storage with a lifetime longer than the function that will retain the memory for you. Something like:



      char* operator"" _cstr(unsigned long long int num)

      // Note: you *can't* use a vector<string>, because string's
      // memory may be invalidated on moves, like if the vector
      // resizes.
      static auto storage = std::vector<std::unique_ptr<char>>;

      auto digits = std::string;
      do

      digits += '0' + (num % 10);
      num /= 10;
      while (num != 0);

      auto p = std::make_unique<char>(digits.size() + 1);

      std::reverse_copy(digits.cbegin(), digits.cend(), p.get());
      p[digits.size()] = '';

      // Get the char*, and then move the unique_ptr into storage
      // to let it be managed there.
      auto result = p.get();
      storage.push_back(std::move(p));

      return result;



      This will "work", and it's basically what some C libraries do (when they don't simply leak). But with no way to release the memory, it will just grow and grow. You could add a mechanism to release the memory, but you would need a way to be sure it's safe for every string. This solution is possible, but unwieldy.



      The last solution - the worst case (other than outright UB) - is to define some sort of contract that client code must follow. In plain English, document the function by saying "you must delete the memory when you're done with it". That requires programmers to behave, and is very easy to break, so it is not recommended. But... if you really want a C-string function, it's an option. A bad one, but an option nonetheless.



      Summary



      You have one critical error in your code:



      • In ""_cstr(unsigned long long), you return a pointer to the internal memory of a std::string that gets destroyed at the end of the function, meaning you're returning a pointer to invalid memory.

      You have a couple of logical errors in your code:



      • Both functions will return an empty string when the number is zero, instead of "0".

      You have a few inefficiencies:



      • You use deques because you want to add the numbers in reverse order, and reason that makes sense because neither strings nor vectors have push_front(). But that's all unnecessary. You can simply push_back() directly into your result string, and then use std::reverse(). (Or push_back() into a string buffer, and std::reverse_copy() into your final buffer.)

      And here are some tips to keep in mind:



      It's really good that you're doing testing, but if you're going to do that, you might as well go all in and use something like Google Test. It's header-only, a single include, and it produces very pretty output. A full battery of tests can give you great confidence in your code. Using Google Test might look something like (fair warning, I don't actually use Google Test that often):



      TEST(StrTest, HandlesZero) 
      EXPECT_EQ(0_str, "0");



      To test that the types are right, rather than trying to demangle the names (which, by the way, it's been a long time since I've used abi::__cxa_demangle(), but I'm pretty sure you're not using it correctly), it's so much easier to just use type traits:



      TEST(StrTest, ReturnType) 
      EXPECT_TRUE((std::is_same_v<decltype(0_str), std::string>));



      Another dirty trick you can use to examine types is deliberately generating errors:



      template <typename T>
      struct TD; // note, never define TD

      TD<decltype(0_str)> t; // will trigger an error


      The error generated will print the exact type of whatever is in the angle brackets, on every compiler. This is a hackish way to check types, but it can help when you're experimenting.






      share|improve this answer















      I'm ignoring all the testing stuff, and just focusing on the two conversion functions.



      std::string operator"" _str(unsigned long long int num)
      {
      std::deque<char> digits;


      There's really no need for the deque. You can push_back() all the digits directly into a std::string, and then use std::reverse().



      To make it even better, you could use the max value of an unsigned long long and calculate the maximum number of digits you'll need, then use reserve(). But that probably won't net you any real gains in reality, since you'll likely always be under the small-string optimization size where std::string won't actually allocate any memory... another reason to ditch the deque and stick with strings.



      for(char i = 0; num != 0;num = num / 10)


      Have you tested your conversion functions with zero? Because if num is zero, this loop won't fire at all, and digits will be empty. So "0"_str will result in "".



      This is a pretty weird and confusing loop in general. i looks like it's going to be some kind of loop counter, but it's not. All-in-all, this just looks like a case of trying to be too clever, and outsmarting yourself (due to the zero issue).



      This could be expressed much more simply and clearly as something like:



      do

      digits.push_front('0' + (num % 10));
      num /= 10;
      while (num != 0);


      and it won't have the zero bug. (do loops are less common than while, but the reason to use one here - and this should be documented in a comment - is to guarantee that even if num is zero, the loop will fire at least once.)



      snipped section about digit character codes



      std::string converternum;


      I already mentioned that the deque is unnecessary, but if you're really going to use it, you should take advantage of the fact that you know digits.size(), and use reserve() for this string to prevent unnecessary allocations.



      char * operator"" _cstr(unsigned long long int num)


      This function is mostly identical to the first one, and thus has the same problems. Again, I would ditch the deque and just work with a string. The digits will be in reverse order, but in this case you can fix that with std::reverse_copy() when you copy them into your output buffer.



      char * string = (char *)converternum.c_str();
      return string;//converternum.c_str();


      Here is where you get into real trouble.



      First, never, ever use C-style casts. In this case, you've cast away the constness of c_str()... that's bad. Prior to C++17, if you want a non-const pointer to std::string's data, you have to jump through some hoops - you have to do something like &convertnum[0] or &convertnum.front() - and I agree that's a pain. But casting away constness is a red flag that won't pass most code reviews, because it is extremely dangerous.



      Second, you are taking a pointer to convertnum's internal data, and returning that... even though convertnum is being destroyed. In other words, you are returning a pointer to freed memory, which is UB.



      How do you solve these problems? Well, the don't answer is: Don't use C-style strings. In other words, don't have a _cstr UDL. It's only going to give you grief (it already has!).



      But if you really, really must... one option is to do what @hoffmale suggests, and instead of char*, return std::unique_ptr<char>. That might look something like this:



      char* operator"" _cstr(unsigned long long int num)

      auto digits = std::string;
      do

      digits += '0' + (num % 10);
      num /= 10;
      while (num != 0);

      auto p = std::make_unique<char>(digits.size() + 1);

      std::reverse_copy(digits.cbegin(), digits.cend(), p.get());
      p[digits.size()] = ''; // have to manually add the null terminator

      return p;



      The problem with that is you're now longer returning a C-string, which makes "_cstr" kind of a lie.



      So another option is to have some kind of storage with a lifetime longer than the function that will retain the memory for you. Something like:



      char* operator"" _cstr(unsigned long long int num)

      // Note: you *can't* use a vector<string>, because string's
      // memory may be invalidated on moves, like if the vector
      // resizes.
      static auto storage = std::vector<std::unique_ptr<char>>;

      auto digits = std::string;
      do

      digits += '0' + (num % 10);
      num /= 10;
      while (num != 0);

      auto p = std::make_unique<char>(digits.size() + 1);

      std::reverse_copy(digits.cbegin(), digits.cend(), p.get());
      p[digits.size()] = '';

      // Get the char*, and then move the unique_ptr into storage
      // to let it be managed there.
      auto result = p.get();
      storage.push_back(std::move(p));

      return result;



      This will "work", and it's basically what some C libraries do (when they don't simply leak). But with no way to release the memory, it will just grow and grow. You could add a mechanism to release the memory, but you would need a way to be sure it's safe for every string. This solution is possible, but unwieldy.



      The last solution - the worst case (other than outright UB) - is to define some sort of contract that client code must follow. In plain English, document the function by saying "you must delete the memory when you're done with it". That requires programmers to behave, and is very easy to break, so it is not recommended. But... if you really want a C-string function, it's an option. A bad one, but an option nonetheless.



      Summary



      You have one critical error in your code:



      • In ""_cstr(unsigned long long), you return a pointer to the internal memory of a std::string that gets destroyed at the end of the function, meaning you're returning a pointer to invalid memory.

      You have a couple of logical errors in your code:



      • Both functions will return an empty string when the number is zero, instead of "0".

      You have a few inefficiencies:



      • You use deques because you want to add the numbers in reverse order, and reason that makes sense because neither strings nor vectors have push_front(). But that's all unnecessary. You can simply push_back() directly into your result string, and then use std::reverse(). (Or push_back() into a string buffer, and std::reverse_copy() into your final buffer.)

      And here are some tips to keep in mind:



      It's really good that you're doing testing, but if you're going to do that, you might as well go all in and use something like Google Test. It's header-only, a single include, and it produces very pretty output. A full battery of tests can give you great confidence in your code. Using Google Test might look something like (fair warning, I don't actually use Google Test that often):



      TEST(StrTest, HandlesZero) 
      EXPECT_EQ(0_str, "0");



      To test that the types are right, rather than trying to demangle the names (which, by the way, it's been a long time since I've used abi::__cxa_demangle(), but I'm pretty sure you're not using it correctly), it's so much easier to just use type traits:



      TEST(StrTest, ReturnType) 
      EXPECT_TRUE((std::is_same_v<decltype(0_str), std::string>));



      Another dirty trick you can use to examine types is deliberately generating errors:



      template <typename T>
      struct TD; // note, never define TD

      TD<decltype(0_str)> t; // will trigger an error


      The error generated will print the exact type of whatever is in the angle brackets, on every compiler. This is a hackish way to check types, but it can help when you're experimenting.







      share|improve this answer















      share|improve this answer



      share|improve this answer








      edited Jul 13 at 8:22









      Toby Speight

      17.2k13486




      17.2k13486











      answered Jul 13 at 2:53









      indi

      3,021417




      3,021417











      • C++ guarantees that digits are contiguous. [lex.charset]/3: "In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous."
        – Jerry Coffin
        Jul 13 at 3:07










      • That's good to know. I'm battle-scarred after EBCDIC and its non-contiguous letters.
        – indi
        Jul 13 at 3:13











      • I certainly can't blame you for that!
        – Jerry Coffin
        Jul 13 at 3:14






      • 1




        For tests of type equality, I prefer to do that at compile-time - e.g. static_assert(std::is_same_v<decltype(0_str), std::string>);
        – Toby Speight
        Jul 13 at 8:26










      • In case it's not obvious - the static_assert can still be in the test method. Example in a recent question of mine: Compute mean and variance, incrementally.
        – Toby Speight
        Jul 13 at 15:02
















      • C++ guarantees that digits are contiguous. [lex.charset]/3: "In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous."
        – Jerry Coffin
        Jul 13 at 3:07










      • That's good to know. I'm battle-scarred after EBCDIC and its non-contiguous letters.
        – indi
        Jul 13 at 3:13











      • I certainly can't blame you for that!
        – Jerry Coffin
        Jul 13 at 3:14






      • 1




        For tests of type equality, I prefer to do that at compile-time - e.g. static_assert(std::is_same_v<decltype(0_str), std::string>);
        – Toby Speight
        Jul 13 at 8:26










      • In case it's not obvious - the static_assert can still be in the test method. Example in a recent question of mine: Compute mean and variance, incrementally.
        – Toby Speight
        Jul 13 at 15:02















      C++ guarantees that digits are contiguous. [lex.charset]/3: "In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous."
      – Jerry Coffin
      Jul 13 at 3:07




      C++ guarantees that digits are contiguous. [lex.charset]/3: "In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous."
      – Jerry Coffin
      Jul 13 at 3:07












      That's good to know. I'm battle-scarred after EBCDIC and its non-contiguous letters.
      – indi
      Jul 13 at 3:13





      That's good to know. I'm battle-scarred after EBCDIC and its non-contiguous letters.
      – indi
      Jul 13 at 3:13













      I certainly can't blame you for that!
      – Jerry Coffin
      Jul 13 at 3:14




      I certainly can't blame you for that!
      – Jerry Coffin
      Jul 13 at 3:14




      1




      1




      For tests of type equality, I prefer to do that at compile-time - e.g. static_assert(std::is_same_v<decltype(0_str), std::string>);
      – Toby Speight
      Jul 13 at 8:26




      For tests of type equality, I prefer to do that at compile-time - e.g. static_assert(std::is_same_v<decltype(0_str), std::string>);
      – Toby Speight
      Jul 13 at 8:26












      In case it's not obvious - the static_assert can still be in the test method. Example in a recent question of mine: Compute mean and variance, incrementally.
      – Toby Speight
      Jul 13 at 15:02




      In case it's not obvious - the static_assert can still be in the test method. Example in a recent question of mine: Compute mean and variance, incrementally.
      – Toby Speight
      Jul 13 at 15:02












      up vote
      5
      down vote














      Note: Intermingling actual implementation code and testing code/usage example makes for bad reading.




      std::string has a constructor that takes two iterators and constructs its contents from them. This allows _str to be simplified to:



      std::string operator"" _str(unsigned long long int num)

      std::deque<char> digits;

      for(; num != 0; num /= 10)

      auto digit = '0' + (num % 10);
      digits.push_front(digit);


      return std::string std::begin(digits), std::end(digits) ;



      Similar applies to _cstr. But beware! Since converternum in _cstr gets destructed once the function returns, the pointer returned by c_str() earlier will dangle. To return a valid char*, a copy has to be made - and in that case, the memory could be managed directly anyways:



      char* operator"" _cstr(unsigned long long int num)

      std::deque<char> digits;

      for(; num != 0; num /= 10)

      char digit = char'0'+(num % 10);
      digits.push_front(digit);


      auto string = new char[digits.size() + 1];

      std::copy(std::begin(digits), std::end(digits), string);
      string[digits.size()] = '';

      return string;






      share|improve this answer





















      • Thank you, I'm trying to learn more about the standard lib algorithms for these classes and appreciate your help in doing so
        – Meme myself and a very creepy
        Jul 13 at 1:09










      • @Mememyselfandaverycreepy: Generally, I'd suggest returning a std::unique_ptr<char>, but that isn't a C-style string. If you need C-style strings (e.g. for calling C code), you might want to think about exchanging new char[...] for malloc (C can call free, but it doesn't have access to delete)
        – hoffmale
        Jul 13 at 1:12














      up vote
      5
      down vote














      Note: Intermingling actual implementation code and testing code/usage example makes for bad reading.




      std::string has a constructor that takes two iterators and constructs its contents from them. This allows _str to be simplified to:



      std::string operator"" _str(unsigned long long int num)

      std::deque<char> digits;

      for(; num != 0; num /= 10)

      auto digit = '0' + (num % 10);
      digits.push_front(digit);


      return std::string std::begin(digits), std::end(digits) ;



      Similar applies to _cstr. But beware! Since converternum in _cstr gets destructed once the function returns, the pointer returned by c_str() earlier will dangle. To return a valid char*, a copy has to be made - and in that case, the memory could be managed directly anyways:



      char* operator"" _cstr(unsigned long long int num)

      std::deque<char> digits;

      for(; num != 0; num /= 10)

      char digit = char'0'+(num % 10);
      digits.push_front(digit);


      auto string = new char[digits.size() + 1];

      std::copy(std::begin(digits), std::end(digits), string);
      string[digits.size()] = '';

      return string;






      share|improve this answer





















      • Thank you, I'm trying to learn more about the standard lib algorithms for these classes and appreciate your help in doing so
        – Meme myself and a very creepy
        Jul 13 at 1:09










      • @Mememyselfandaverycreepy: Generally, I'd suggest returning a std::unique_ptr<char>, but that isn't a C-style string. If you need C-style strings (e.g. for calling C code), you might want to think about exchanging new char[...] for malloc (C can call free, but it doesn't have access to delete)
        – hoffmale
        Jul 13 at 1:12












      up vote
      5
      down vote










      up vote
      5
      down vote










      Note: Intermingling actual implementation code and testing code/usage example makes for bad reading.




      std::string has a constructor that takes two iterators and constructs its contents from them. This allows _str to be simplified to:



      std::string operator"" _str(unsigned long long int num)

      std::deque<char> digits;

      for(; num != 0; num /= 10)

      auto digit = '0' + (num % 10);
      digits.push_front(digit);


      return std::string std::begin(digits), std::end(digits) ;



      Similar applies to _cstr. But beware! Since converternum in _cstr gets destructed once the function returns, the pointer returned by c_str() earlier will dangle. To return a valid char*, a copy has to be made - and in that case, the memory could be managed directly anyways:



      char* operator"" _cstr(unsigned long long int num)

      std::deque<char> digits;

      for(; num != 0; num /= 10)

      char digit = char'0'+(num % 10);
      digits.push_front(digit);


      auto string = new char[digits.size() + 1];

      std::copy(std::begin(digits), std::end(digits), string);
      string[digits.size()] = '';

      return string;






      share|improve this answer














      Note: Intermingling actual implementation code and testing code/usage example makes for bad reading.




      std::string has a constructor that takes two iterators and constructs its contents from them. This allows _str to be simplified to:



      std::string operator"" _str(unsigned long long int num)

      std::deque<char> digits;

      for(; num != 0; num /= 10)

      auto digit = '0' + (num % 10);
      digits.push_front(digit);


      return std::string std::begin(digits), std::end(digits) ;



      Similar applies to _cstr. But beware! Since converternum in _cstr gets destructed once the function returns, the pointer returned by c_str() earlier will dangle. To return a valid char*, a copy has to be made - and in that case, the memory could be managed directly anyways:



      char* operator"" _cstr(unsigned long long int num)

      std::deque<char> digits;

      for(; num != 0; num /= 10)

      char digit = char'0'+(num % 10);
      digits.push_front(digit);


      auto string = new char[digits.size() + 1];

      std::copy(std::begin(digits), std::end(digits), string);
      string[digits.size()] = '';

      return string;







      share|improve this answer













      share|improve this answer



      share|improve this answer











      answered Jul 13 at 0:57









      hoffmale

      4,205630




      4,205630











      • Thank you, I'm trying to learn more about the standard lib algorithms for these classes and appreciate your help in doing so
        – Meme myself and a very creepy
        Jul 13 at 1:09










      • @Mememyselfandaverycreepy: Generally, I'd suggest returning a std::unique_ptr<char>, but that isn't a C-style string. If you need C-style strings (e.g. for calling C code), you might want to think about exchanging new char[...] for malloc (C can call free, but it doesn't have access to delete)
        – hoffmale
        Jul 13 at 1:12
















      • Thank you, I'm trying to learn more about the standard lib algorithms for these classes and appreciate your help in doing so
        – Meme myself and a very creepy
        Jul 13 at 1:09










      • @Mememyselfandaverycreepy: Generally, I'd suggest returning a std::unique_ptr<char>, but that isn't a C-style string. If you need C-style strings (e.g. for calling C code), you might want to think about exchanging new char[...] for malloc (C can call free, but it doesn't have access to delete)
        – hoffmale
        Jul 13 at 1:12















      Thank you, I'm trying to learn more about the standard lib algorithms for these classes and appreciate your help in doing so
      – Meme myself and a very creepy
      Jul 13 at 1:09




      Thank you, I'm trying to learn more about the standard lib algorithms for these classes and appreciate your help in doing so
      – Meme myself and a very creepy
      Jul 13 at 1:09












      @Mememyselfandaverycreepy: Generally, I'd suggest returning a std::unique_ptr<char>, but that isn't a C-style string. If you need C-style strings (e.g. for calling C code), you might want to think about exchanging new char[...] for malloc (C can call free, but it doesn't have access to delete)
      – hoffmale
      Jul 13 at 1:12




      @Mememyselfandaverycreepy: Generally, I'd suggest returning a std::unique_ptr<char>, but that isn't a C-style string. If you need C-style strings (e.g. for calling C code), you might want to think about exchanging new char[...] for malloc (C can call free, but it doesn't have access to delete)
      – hoffmale
      Jul 13 at 1:12










      up vote
      4
      down vote













      The other reviews have already covered the most important bits, so I'll just address a few things not already mentioned.



      Use your own function



      If you have strdup available, the operator""_cstr can become a one-liner:



      char * operator"" _cstr(unsigned long long int num) 
      return strdup(operator""_str(num).c_str());



      If you don't have strdup (it's a standard POSIX, but not standard C++ function), one could easily create one or simply write the few lines of code to create the equivalent. Alternatively, one could use the other operator as a base from which to construct a string, but I like this direction better. In fact, I'd probably not provide a cstr operator at all because of the ugliness it introduces for memory management.



      Perform static checking



      Instead of using abi::__cxa_demangle() which is, as you know, non-portable, we can do this:



      static_assert(std::is_same<decltype(str), std::string>(), 
      "str does not match expected type 'std::string'n");
      static_assert(std::is_same<decltype(cstr), char *>(),
      "cstr does not match expected type 'char *'n");


      One could even go crazy and define an ugly macro for this:



      #define TYPE_CHECK(str, tipo) 
      static_assert(std::is_same<decltype(str), tipo>(),
      # str " does not match expected type '" # tipo "'n")


      Then use it as:



      TYPE_CHECK(str, std::string);
      TYPE_CHECK(cstr, const char *);





      share|improve this answer



























        up vote
        4
        down vote













        The other reviews have already covered the most important bits, so I'll just address a few things not already mentioned.



        Use your own function



        If you have strdup available, the operator""_cstr can become a one-liner:



        char * operator"" _cstr(unsigned long long int num) 
        return strdup(operator""_str(num).c_str());



        If you don't have strdup (it's a standard POSIX, but not standard C++ function), one could easily create one or simply write the few lines of code to create the equivalent. Alternatively, one could use the other operator as a base from which to construct a string, but I like this direction better. In fact, I'd probably not provide a cstr operator at all because of the ugliness it introduces for memory management.



        Perform static checking



        Instead of using abi::__cxa_demangle() which is, as you know, non-portable, we can do this:



        static_assert(std::is_same<decltype(str), std::string>(), 
        "str does not match expected type 'std::string'n");
        static_assert(std::is_same<decltype(cstr), char *>(),
        "cstr does not match expected type 'char *'n");


        One could even go crazy and define an ugly macro for this:



        #define TYPE_CHECK(str, tipo) 
        static_assert(std::is_same<decltype(str), tipo>(),
        # str " does not match expected type '" # tipo "'n")


        Then use it as:



        TYPE_CHECK(str, std::string);
        TYPE_CHECK(cstr, const char *);





        share|improve this answer

























          up vote
          4
          down vote










          up vote
          4
          down vote









          The other reviews have already covered the most important bits, so I'll just address a few things not already mentioned.



          Use your own function



          If you have strdup available, the operator""_cstr can become a one-liner:



          char * operator"" _cstr(unsigned long long int num) 
          return strdup(operator""_str(num).c_str());



          If you don't have strdup (it's a standard POSIX, but not standard C++ function), one could easily create one or simply write the few lines of code to create the equivalent. Alternatively, one could use the other operator as a base from which to construct a string, but I like this direction better. In fact, I'd probably not provide a cstr operator at all because of the ugliness it introduces for memory management.



          Perform static checking



          Instead of using abi::__cxa_demangle() which is, as you know, non-portable, we can do this:



          static_assert(std::is_same<decltype(str), std::string>(), 
          "str does not match expected type 'std::string'n");
          static_assert(std::is_same<decltype(cstr), char *>(),
          "cstr does not match expected type 'char *'n");


          One could even go crazy and define an ugly macro for this:



          #define TYPE_CHECK(str, tipo) 
          static_assert(std::is_same<decltype(str), tipo>(),
          # str " does not match expected type '" # tipo "'n")


          Then use it as:



          TYPE_CHECK(str, std::string);
          TYPE_CHECK(cstr, const char *);





          share|improve this answer















          The other reviews have already covered the most important bits, so I'll just address a few things not already mentioned.



          Use your own function



          If you have strdup available, the operator""_cstr can become a one-liner:



          char * operator"" _cstr(unsigned long long int num) 
          return strdup(operator""_str(num).c_str());



          If you don't have strdup (it's a standard POSIX, but not standard C++ function), one could easily create one or simply write the few lines of code to create the equivalent. Alternatively, one could use the other operator as a base from which to construct a string, but I like this direction better. In fact, I'd probably not provide a cstr operator at all because of the ugliness it introduces for memory management.



          Perform static checking



          Instead of using abi::__cxa_demangle() which is, as you know, non-portable, we can do this:



          static_assert(std::is_same<decltype(str), std::string>(), 
          "str does not match expected type 'std::string'n");
          static_assert(std::is_same<decltype(cstr), char *>(),
          "cstr does not match expected type 'char *'n");


          One could even go crazy and define an ugly macro for this:



          #define TYPE_CHECK(str, tipo) 
          static_assert(std::is_same<decltype(str), tipo>(),
          # str " does not match expected type '" # tipo "'n")


          Then use it as:



          TYPE_CHECK(str, std::string);
          TYPE_CHECK(cstr, const char *);






          share|improve this answer















          share|improve this answer



          share|improve this answer








          edited Jul 13 at 15:15


























          answered Jul 13 at 14:49









          Edward

          43.9k373200




          43.9k373200




















              up vote
              0
              down vote













              As @indi mentioned you can use (const char *) parameter list. This allows _cstr to be constexpr and simplifies it:



              constexpr const char * operator"" _cstr(const char * cstr) 
              return cstr;



              constexpr here helps you to perform static checking:



              constexpr bool cstring_equal(const char * lhs, const char * rhs) 

              static_assert(cstring_equal(123_cstr, "123"), "123_cstr doesn't match expected value "123"");


              Also (const char *) parameter list allows floating-point: 12.3_cstr.






              share|improve this answer

























                up vote
                0
                down vote













                As @indi mentioned you can use (const char *) parameter list. This allows _cstr to be constexpr and simplifies it:



                constexpr const char * operator"" _cstr(const char * cstr) 
                return cstr;



                constexpr here helps you to perform static checking:



                constexpr bool cstring_equal(const char * lhs, const char * rhs) 

                static_assert(cstring_equal(123_cstr, "123"), "123_cstr doesn't match expected value "123"");


                Also (const char *) parameter list allows floating-point: 12.3_cstr.






                share|improve this answer























                  up vote
                  0
                  down vote










                  up vote
                  0
                  down vote









                  As @indi mentioned you can use (const char *) parameter list. This allows _cstr to be constexpr and simplifies it:



                  constexpr const char * operator"" _cstr(const char * cstr) 
                  return cstr;



                  constexpr here helps you to perform static checking:



                  constexpr bool cstring_equal(const char * lhs, const char * rhs) 

                  static_assert(cstring_equal(123_cstr, "123"), "123_cstr doesn't match expected value "123"");


                  Also (const char *) parameter list allows floating-point: 12.3_cstr.






                  share|improve this answer













                  As @indi mentioned you can use (const char *) parameter list. This allows _cstr to be constexpr and simplifies it:



                  constexpr const char * operator"" _cstr(const char * cstr) 
                  return cstr;



                  constexpr here helps you to perform static checking:



                  constexpr bool cstring_equal(const char * lhs, const char * rhs) 

                  static_assert(cstring_equal(123_cstr, "123"), "123_cstr doesn't match expected value "123"");


                  Also (const char *) parameter list allows floating-point: 12.3_cstr.







                  share|improve this answer













                  share|improve this answer



                  share|improve this answer











                  answered Jul 14 at 17:49









                  Artem

                  1




                  1






















                       

                      draft saved


                      draft discarded


























                       


                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function ()
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f198393%2fremaking-stdto-string-in-c-with-udls-user-defined-literals%23new-answer', 'question_page');

                      );

                      Post as a guest













































































                      Popular posts from this blog

                      Chat program with C++ and SFML

                      Function to Return a JSON Like Objects Using VBA Collections and Arrays

                      Will my employers contract hold up in court?