I read that string literals have static storage duration, does that mean that references to them are always valid? For example, is the following code safe:
std::function<void()> foo(const std::string& name){
// capture name by reference
return [&](){
std::cout<<"The name is "<<name<<std::endl;
};
}
int main() {
std::function<void()> func;
{
func = foo("test");
func(); // func called in the same scope
}
func(); // func called in the outer scope
}
What if instead we first create a string from that string literal, and reference that:
std::function<void()> foo(const std::string& name){
// capture name by reference
return [&](){
std::cout<<"The name is "<<name<<std::endl;
};
}
int main() {
std::function<void()> func;
{
std::string test = "test";
func = foo(test);
func(); // func called in the same scope
}
func(); // func called in the outer scope
}
No, this is not safe.
name is not a reference to a string literal. String literals are not std::string objects, so a temporary, unnamed std::string object object is constructed and name is a reference to that object. Your lambda then captures a reference to that same temporary object.
Such temporary objects' lifetime ends at the end of the full expression in which they're created, so that object's lifetime ends after the func = foo("test"); expression. If you call func after that point then you will access the temporary object after its lifetime has ended, which results in undefined behavior.
The behavior of your second program is also undefined. Your lambda holds a reference to test, and that object's lifetime ends at the end of its enclosing scope. Calling func after that point means accessing test after its lifetime has ended, leading to undefined behavior.
String literals are not std::string objects. They are simple arrays of const chars. That is, the type of "test" is const char[5]. That array has static storage duration. If you were to capture a reference to that array then you would be safe; i.e. the following is well-defined:
// -----------------------vvvvvvvvvvvvvvvvvvvvv Note this type is changed
std::function<void()> foo(const char (&name)[5]){
// capture name by reference
return [&](){
std::cout<<"The name is "<<name<<std::endl;
};
}
int main() {
std::function<void()> func;
{
func = foo("test");
func(); // func called in the same scope
}
func(); // func called in the outer scope
}
template<class C, std::size_t N>
struct ct_string:std::array<C, N> {
constexpr ct_string():
std::array<C,N>{0}
{}
constexpr ct_string( const C(&str)[N] ):
ct_string()
{
char* pdest = static_cast<std::array<C, N>*>(this)->begin();
std::copy(std::begin(str), std::end(str), pdest);
}
constexpr auto end() const { return this->begin() + length(); }
constexpr auto end() { return this->begin() + length(); }
constexpr C const* toString() const { return this->data(); }
static constexpr std::size_t length() { return N; }
template<std::size_t N0, std::size_t N1>
friend constexpr ct_string<C, N0+N1> operator+( ct_string<C, N0> lhs, ct_string<C, N1> rhs ) {
ct_string<C, N0+N1> retval;
std::copy( lhs.begin(), lhs.end(), retval.begin() );
std::copy( rhs.begin(), rhs.end(), retval.begin() + lhs.length() );
retval[N0+N0] = 0;
return retval;
}
};
template<ct_string str>
constexpr decltype(auto) operator ""_ct() {
return str;
}
template<ct_string str>
std::string const& operator ""_ss() {
static const std::string retval{str.begin(), str.end()};
return retval;
}
this lets you do this:
std::function<void()> foo(const std::string& name){
// capture name by reference
return [&](){
std::cout<<"The name is "<<name<<std::endl;
};
}
int main() {
std::function<void()> func;
{
func = foo("test"_ss);
func(); // func called in the same scope
}
func(); // func called in the outer scope
}
without problems. Without a technique such as this, while the "test" array of characters is static storage duration, the std::string constructed from it is not.
The technique I have described first (a) converts "test" into a compile-time string, (b) for each call to "some_string"_ss creates a distinct instance of operator ""_ss<...> template function, (c) creates a const std::string of static storage duration within each of those template function instantiations containing the string in question, (d) returns a const& to them.
This std::string is of static storage duration, so can be used like you do in your code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With