Auto with String Literals

auto with string literals

The feature auto is based on template argument deduction and template argument deduction behaves the same, specifically according to §14.8.2.1/2 (C++11 standard):

  • If P is not a reference type
    • If A is an array type, the pointer type produced by the array-to-pointer conversion is used in place of A for type deduction

If you want the type of the expression x to be an array type, just add & after auto:

auto& x = "Hello world!";

Then, the auto placeholder will be deduced to be const char[13]. This is also similar to function templates taking a reference as parameter. Just to avoid any confusion: The declared type of x will be reference-to-array.

String literals inside functions: automatic variables or allocated in heap?

String literals will be placed in the initialized data or text (code) segment of your binary by the compiler, rather than residing in (runtime allocated) memory or the stack. So you should be using a pointer, since you're going to be referencing the string literal that the compiler has already produced for you. Note that modifying this (which would require changing memory protection typically) will change all uses of this literal.

Why does auto declare strings as const char* instead of std::string?

The reason you can't "write" to your auto variable is that it's a const char * or const char [1], because that is the type of any string constant.

The point of auto is to resolve to the simplest possible type which "works" for the type of the assignment. The compiler does not "look forward to see what you are doing with the variable", so it doesn't understand that later on you will want to write into this variable, and use it to store a string, so std::string would make more sense.

You code could be made to work in many different ways, here's one that makes some sense:

std::string default_name = "";
auto name = default_name;

cin >> name;

C: do all string literals have static storage duration?

I've been reading in various sources that string literals remain in
memory for the whole lifetime of the program.

Yes.

In that case, what is
the difference between those two functions

char *f1() { return "hello"; }
char *f2() {
char str[] = "hello";
return str;
}

f1 returns a pointer to the first element of the array represented by a string literal, which has static storage duration. f2 returns a pointer to the first element of the automatic array str. str has a string literal for an initializer, but it is a separate object.

While f1 compiles fine, f2 complains that I'm returning stack
allocated data. What happens here?

  • if the str points to the actual string literal (which has static duration), why do I get an error?

It does not. In fact, it itself does not point to anything. It is an array, not a pointer.

  • if the string literal is copied to the local variable str, where does the original string literal go? does it remain in memory with no
    reference to it?

C does not specify, but in practice, yes, some representation of the string literal must be stored somewhere in the program, perhaps in the function implementation, because it needs to be used to initialize str anew each time f2 is called.

Why passing a string literal to a template calling std::format fails to compile?

After P2216, std::format requires that the format string must be a core constant expression. In your case, the compilation fails because the function argument First is not a constant expression.

The workaround is to use std::vformat, which works for runtime format strings

template<typename First, typename... Args>
auto format1(First&& first, Args&&... args) {
return std::vformat(
std::forward<First>(first),
std::make_format_args(std::forward<Args>(args)...));
}

Demo

If you really want to use std::format, you can pass in a lambda that returns a string literal

template<typename First, typename... Args>
auto format1(First first, Args&&... args) {
return std::format(first(), std::forward<Args>(args)...);
}

int main() {
std::cout << format1([]{ return "hi {} {}"; }, 123, 456);
}

Demo

Advantages of using user-defined literal for strings instead of string literal

If you're part of the "Almost Always Auto" crowd, then the UDL is very important. It lets you do this:

auto str = "Foo"s;

And thus, str will be a genuine std::string, not a const char*. It therefore permits you to decide when to do which.

This is also important for auto return type deduction:

[]() {return "Foo"s;}

Or any form of type deduction, really:

template<typename T>
void foo(T &&t) {...}

foo("Foo"s);

The only advantage I see using [...] instead of [...] is that in the first case the compiler can perform copy-elision (I think), which would be faster than the constructor call in the second case.

Copy-elision is not faster than the constructor call. Either way, you're calling one of the object's constructors. The question is which one:

std::string str = "foo";

This will provoke a call to the constructor of std::string which takes a const char*. But since std::string has to copy the string into its own storage, it must get the length of the string to do so. And since it doesn't know the length, this constructor is forced to use strlen to get it (technically, char_traits<char>::length, but that's probably not going to be much faster).

By contrast:

std::string str = "foo"s;

This will use the UDL template that has this prototype:

string operator "" s(const char* str, size_t len);

See, the compiler knows the length of a string literal. So the UDL code is passed a pointer to the string and a size. And thus, it can call the std::string constructor that takes a const char* and a size_t. So there's no need for computing the string's length.

The advice in question is not for you to go around and convert every use of a literal into the s version. If you're fine with the limitations of an array of chars, use it. The advice is that, if you're going to store that literal in a std::string, it's best to get that done while it's still a literal and not a nebulous const char*.

Why does using string instead of auto fix this error?

"hello", being a string literal, is of type const char*, not std::string.

When you do this:

auto st = "hello";

The type of st is deduced as const char*, not std::string.

When you do this instead:

string st = "hello";

It invokes std::string's constructor that takes in a const char* pointer, which will copy the chars from the literal into the new std::string instance.



Related Topics



Leave a reply



Submit