最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - r-value reference to default argument temporary to hold the buffer for converting a wstring? - Stack Overflow

programmeradmin5浏览0评论

Background

I have code with a logging interface that takes const char* only.

My code deals with a mixed bag of std::string and std::wstring coming from different sources.

And, yes, I know that this is far from ideal, but let's focus on the technicalities.

Problem

I would like to feed both the std::string and std::wstring to a uniform conversion function, let's call it tc_str(T StrT) to ease construction of logging expressions.

For string, the implementation is straightforward:

const char* tc_str(const std::string& str) {
    return str.c_str();
}

For wstring, I have come up with the following solution that works on VS2022 afaict:

const char* tc_str(const std::wstring& wstr, std::string&& rTmpLifeExt = std::string()) {
    rTmpLifeExt = wchar_to_narrow(wstr);
    return rTmpLifeExt.c_str();
}

Here's the full test code Gist.

Quick usage snippet:

std::string narrow = ...
std::wstring wide = ...

printf("n: %s / w: %s", tc_str(narrow), tc_str(wide));

Question

Is this legal C++20?

According to (ref cppref) (ref: §12.2 [class.temporary]; §1.9 [intro.execution]) the reference lifetime should be extended "correctly" and no dangling pointers should occur:

... special case in §12.2 [class.temporary]:

p4 ... contexts in which temporaries are destroyed at a different point [...]

p5 ... when a reference is bound to a temporary. ...:

  • A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.

... in §1.9 [intro.execution] p11:

[ Note: ... subexpressions involved in evaluating default arguments (8.3.6) are considered to be created in the expression that calls the function, not the expression that defines the default argument. —end note ]

My biggest doubt would be with r-value vs. l-value references here, but it seems they are treated the same.

Given the constraint of that crappy logging API, is this a "good enough" solution or are there any major problems with it?

Background

I have code with a logging interface that takes const char* only.

My code deals with a mixed bag of std::string and std::wstring coming from different sources.

And, yes, I know that this is far from ideal, but let's focus on the technicalities.

Problem

I would like to feed both the std::string and std::wstring to a uniform conversion function, let's call it tc_str(T StrT) to ease construction of logging expressions.

For string, the implementation is straightforward:

const char* tc_str(const std::string& str) {
    return str.c_str();
}

For wstring, I have come up with the following solution that works on VS2022 afaict:

const char* tc_str(const std::wstring& wstr, std::string&& rTmpLifeExt = std::string()) {
    rTmpLifeExt = wchar_to_narrow(wstr);
    return rTmpLifeExt.c_str();
}

Here's the full test code Gist.

Quick usage snippet:

std::string narrow = ...
std::wstring wide = ...

printf("n: %s / w: %s", tc_str(narrow), tc_str(wide));

Question

Is this legal C++20?

According to https://stackoverflow/a/12554621/321013 (ref cppref) (ref: §12.2 [class.temporary]; §1.9 [intro.execution]) the reference lifetime should be extended "correctly" and no dangling pointers should occur:

... special case in §12.2 [class.temporary]:

p4 ... contexts in which temporaries are destroyed at a different point [...]

p5 ... when a reference is bound to a temporary. ...:

  • A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.

... in §1.9 [intro.execution] p11:

[ Note: ... subexpressions involved in evaluating default arguments (8.3.6) are considered to be created in the expression that calls the function, not the expression that defines the default argument. —end note ]

My biggest doubt would be with r-value vs. l-value references here, but it seems they are treated the same.

Given the constraint of that crappy logging API, is this a "good enough" solution or are there any major problems with it?

Share Improve this question edited 17 hours ago Yksisarvinen 22.3k5 gold badges33 silver badges66 bronze badges asked 17 hours ago Martin BaMartin Ba 38.8k35 gold badges197 silver badges358 bronze badges 4
  • 2 It should work; "is this a "good enough" solution", it is up to you to see. const char* dangling = tc_str(wide); foo(dangling); would be problematic. – Jarod42 Commented 17 hours ago
  • 2 Yes, this looks good enough. I use this too. I'm not sure what answers you want, since you already cited all the right paragraphs. – HolyBlackCat Commented 17 hours ago
  • 1 @HolyBlackCat - I guess "looks, good, I use this too" is already a very valuable comment. I guess useful answers would either disprove the technical validity or point out a major usage-scenario flaw. Otherwise, "We came to the same conclusion and use this technique in production with compiler X, Y and Z since n years." would also answer both questions. – Martin Ba Commented 16 hours ago
  • 1 It is valid, but if you do not expose those tc_str overloads in interface then unneeded but if you expose those then bear trap. Can be someone uses the dangerous case like Jarod42 commented and nothing detects it besides AddressSanitiser. – Öö Tiib Commented 13 hours ago
Add a comment  | 

1 Answer 1

Reset to default 0

Might I suggest a solution with less magic?

struct string_c_helper {
  std::string storage;
  char const* string_ptr = "";
  operator char const*() const { return string_ptr; }
  bool uses_storage() const { return string_ptr == storage.c_str(); }

  string_c_helper() {} // empty string

  string_c_helper( char const* ptr ):string_ptr(ptr) {}
  string_c_helper( std::string const& str):string_ptr(str.c_str()) {}

  struct store_string_t {};
  string_c_helper( store_string_t, std::string str ):
    storage(std::move(str)), string_ptr(storage.c_str())
  {}

  // use storage for wstrings:
  string_c_helper( std::wstring const& wstr):
    string_c_helper(store_string_t{}, (wchar_to_narrow(wstr))
  {}

  // allow move construction.  If it uses storage, copy the
  // storage, otherwise just copy the pointer:
  string_c_helper(string_c_helper&& o):
    string_c_helper(
      o.uses_storage()?
        string_c_helper(store_string_t{}, std::move(o.storage))
      :
        string_c_helper(o.string_ptr)
    )
  {}

  string_c_helper(string_c_helper const&)=delete;
  string_c_helper& operator=(string_c_helper const&)& = delete;
  string_c_helper& operator=(string_c_helper&& o)& = delete;
};
    

this type is a char const* and optionally a std::string to carry it. It can be implicitly cast to char const* regardless. It supports empty strings.

string_c_helper tc_str(const std::wstring& wstr) { return wstr; } string_c_helper tc_str(const std::string& str) { return str; } you can pass these to C-api objects. You can even return them from functions. Only once converted to a char const*, the validity ends at the end of the full-expression the conversion occurs at.

The same is basically true of yours, but the issue with yours is that working out why it works will confuse most C++ developers. So when it breaks, they'll have little hope of understanding what went wrong.

This basically "manually" does the string lifetime extension instead of having it occur magically.

(Aside: string_c_helper(string_c_helper&&) isn't infinite recursion because of guaranteed elision.)

发布评论

评论列表(0)

  1. 暂无评论