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 |1 Answer
Reset to default 0Might 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.)
const char* dangling = tc_str(wide); foo(dangling);
would be problematic. – Jarod42 Commented 17 hours ago