How to properly pass C strings to a lambda inside a variadic template function
Here is my complex situation:
I have a function using variadic template and lambda:
template<typename...Args>
void foo(Args...args) {
// the arguments are passed to the lambda
_func = [args...](){ do_sth(args...); };
}
On observing specific events, the lambda _func
would be fired. My problem is, some of the arguments I passed are C strings, they could be pointing to temporary std string like this:
const char *pStr = __temp_std__string__.c_str();
Given I call foo(pStr);
, and when _func
is called, the temporary string that pStr is pointing to, has been released. I would like to know whether there exists a generic way to handle this. I am using C++11.
EDIT:
Perhaps I should post my whole story, as many of you advise to pass std::string instead of C strings, there are reasons that I can't escape from it.
I am developing games using cocos2d-x, which deploys C++11. What I want to do is to support auto-localisation of labels when players change their preferences of languages (selected from a UI).
I have saved the text in a couple of files, and each of them contains the localised text of a single language, they are basically under the following structure:
{
"key1" : "_localized_text1_",
"key2" : "_localized_text2_",
...
}
The idea is to observe the event on change of language's preference (through a notification), and I would get a key indicating that language from it, so as to fetch the localised text from the proper file. Here is the way how I implement it in the object class Label
:
class Label {
// this method would update the label's displayed text
void setString(const std::string& text);
// set a callback for changing language
void setOnLanguageChangeFunc(std::function<void(Notification*)> func);
// set a localised text, which would be updated on changing language
void setLocalizeString(const std::string& key);
};
the core function is setLocalizeString
(I skip the implementations of the other 2 methods as they are intuitive enough from their declaration):
void Label::setLocalizeString(const std::string& key) {
// the callback lambda
auto callback = [=](Notification *pNotification){
setString(LOCALIZED_STRING(key));
}
// assign the lambda
setOnLanguageChangeFunc(callback);
}
where LOCALIZED_STRING
is the macro helper of fetching localised string with a key; and the lambda callback
would be saved as local member variable of Label
in setOnLanguageChangeFunc
.
This works great in most cases, what makes the situation complicated is, there are format specifiers involved in the localised text, for example:
{
...
"keyN" : "%s eats %d cookies",
...
}
Such format placeholders are passed dynamically in codes:
// formatStr = "Tom eats 5 cookies"
std::string formatStr = StringKit::stringWithFormat("%s eats %d cookies", "Tom", 5);
where StringKit
is a utility to format the string, and it accepts variadic arguments which would be passed to vsnprintf
to yield the output. Now you know why I need to pass C string and not std::string, its just due to the underlying method to format string.
Now I have to modify Label::setLocalizeString
so that it could digest the possible variadic arguments:
template<typename... Args>
void setLocalizeString(const std::string& key, Args... args)
{
// the callback lambda
auto callback = [=](Notification *pNotification){
setString(StringKit::stringWithFormat(LOCALIZED_STRING(sKey), args...));
}
// assign the lambda
setOnLanguageChangeFunc(callback);
}
And this is its use case:
// on changing language, the label would display "Tom eats 5 cookies"
pLabel->setLocalizeString("keyN", "Tom", 5);
This case would work like a charm as that C string argument is global, but when it is passed from a temporary std::string :
std::string tempStr = "Tom";
pLabel->setLocalizeString("keyN", tempStr.c_str(), 5);
The C string "Tom" would lose the value on calling the lambda callback, since the pointed std::string, has been gone.
I have tried several ways, like playing with tuple things, or capturing a wrapper class of basic types in the lambda, but none of them could solve the problem. However, I think there should exist some tricky solutions.
This problem is not related to lambdas or variadic functions - it also occurs if you simply store the string:
const char* global_storage;
int main()
{
{
std::string s = "hi";
global_storage = s.c_str();
}
// !!! `global_storage` points to deleted memory!
use(global_storage);
}
You need to make sure the string lives long enough. Using std::string
instead of const char*
is a great starting point:
std::string global_storage;
int main()
{
{
std::string s = "hi";
global_storage = std::move(s);
}
// OK, local string was moved into `global_storage`.
use(global_storage.c_str());
}
If you really need to use a C-style string, just store it in the lambda/whatever as a std::string
, then call .c_str()
when you need to use it, not when storing it.
You need to convert your char const*
arguments to std::string
when storing it in lambda. This is one possible way, i can propose:
#include <iostream>
#include <tuple>
using namespace std;
template<typename T, typename R = conditional_t<is_same<T, char const*>::value, string, T>>
R bar (T &&value) {return value;}
template<class Ch, class Tr, class Tuple, std::size_t... Is>
void print_tuple_impl(std::basic_ostream<Ch,Tr>& os,
const Tuple & t,
std::index_sequence<Is...>)
{
using swallow = int[]; // guaranties left to right order
(void)swallow{0, (void(os << (Is == 0? "" : ", ") << std::get<Is>(t)), 0)...};
}
template<class Ch, class Tr, class... Args>
decltype(auto) operator<<(std::basic_ostream<Ch, Tr>& os,
const std::tuple<Args...>& t)
{
os << "(";
print_tuple_impl(os, t, std::index_sequence_for<Args...>{});
return os << ")";
}
template<typename...Args>
decltype(auto) foo(Args...args)
{
return [args = make_tuple(bar(args)...)] () { cout<< args; return; };
}
int main() {
string *s = new string("Hello, World!");
const char *p = s->c_str();
auto f = foo(1, p, 3.14);
delete s;
f();
return 0;
}
Function foo
returns lambda that stores variadic arguments as tuple, where each char const*
element is converted to std::string
automatically. After that you can free temporary string. It's now should be safe to call that lambda after freeing.
IdeOne.com
链接地址: http://www.djcxy.com/p/51582.html上一篇: 具有可变参数的虚拟方法