Why Is '"Literal"' Encouraged to Decay to 'Const Char*' in C++ Argument Type Match

Why is `literal` encouraged to decay to `const char*` in C++ argument type match?

Why is const char* preferred over const char (&)[N]?

The reason for this is rather technical. Even though the decay of a string literal from const char[N] to const char* is a conversion, it falls into the "lvalue transformation" category and is therefore considered by [over.ics.rank]/3 to be as good as no conversion at all. Since "no conversion" is required for either overload, the non-template overload wins.

Why is const char (&)[N] preferred over const char (&)[] (non-template)?

It is not possible to bind a reference to array of unknown bound to a value of type array of known bound. Instead, a reference to array of unknown bound can only be bound to values that are themselves arrays of unknown bound.

Why is const char (&&)[N] unable to compile?

A string literal is an lvalue so I'm not sure why you would expect this to work.

Is there a "right way" to capture literal strings?

You can use a helper function template that captures its argument using a forwarding reference so as to not destroy any type information (const char* versus const char[N]) then dispatch on the type using template specialization. You'll probably also want to use SFINAE to make sure it is disabled if anything other than a const char* or const char[N] is passed in. To wit,

template <bool b>
struct f_helper;

template <>
struct f_helper<true> {
void do_it(const char*) {
puts("pointer");
}
};

template <>
struct f_helper<false> {
template <std::size_t N>
void do_it(const char (&)[N]) {
printf("array of length %zd\n", N);
}
};

template <class T, class = typename std::enable_if<std::is_same<char*, std::decay_t<T>>::value ||
std::is_same<const char*, std::decay_t<T>>::value>::type>
void f(T&& s) {
f_helper<std::is_pointer<std::remove_reference_t<T>>::value>{}.do_it(s);
}

Coliru link: http://coliru.stacked-crooked.com/a/0e9681868d715e87

Why do string literals (char*) in C++ have to be constants?

Expanding on Christian Gibbons' answer a bit...

In C, string literals, like "Hello, World!", are stored in arrays of char such that they are visible over the lifetime of the program. String literals are supposed to be immutable, and some implementations will store them in a read-only memory segment (such that attempting to modify the literal's contents will trigger a runtime error). Some implementations don't, and attempting to modify the literal's contents may not trigger a runtime error (it may even appear to work as intended). The C language definition leaves the behavior "undefined" so that the compiler is free to handle the situation however it sees fit.

In C++, string literals are stored in arrays of const char, so that any attempt to modify the literal's contents will trigger a diagnostic at compile time.

As Christian points out, the const keyword was not originally a part of C. It was, however, originally part of C++, and it makes using string literals a little safer.

Remember that the const keyword does not mean "store this in read-only memory", it only means "this thing may not be the target of an assignment."

Also remember that, unless it is the operand of the sizeof or unary * operators, or is a string literal used to initialize a character array in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T" and the value of the expression will be the address of the first element of the array.

In C++, when you write

const char *str = "Hello, world";

the address of the first character of the string is stored to str. You can set str to point to a different string literal:

str = "Goodbye cruel world";

but what you cannot do is modify the contents of the string, something like

str[0] = 'h';

or

strcpy( str, "Something else" );

Default argument of type const char * is incompatible with parameter of type char *

Depending on compiler, C-style string literals may be allocated in readonly memory. Thus they are const char[N+1] (N is the string length) (which is implicitly convertible to const char* because of array to pointer decay).

The problem you're having is that it's illegal to drop const qualifiers (with the exception of the infamous const_cast or equivalent C-style cast).

Since you're only reading from sir, you can fix this by making sir be const char* instead, which doesn't violate const:

class student {
...
student(int = 8, const char* =" "); // problem solved
...
};

student::student(int nr_marks, const char* sir) : // remember to change it here as well
marks(nr_marks)
{
strcpy(name, sir);
}

Why does we have a type mismatch?

You are passing const char[7] not const char *. Arrays and pointers are not the same things. They are often confused because arrays easily decay to pointers to their first element. When taken by reference, arrays don't need to decay to pointers. Only in the first case does your array need to decay to a pointer.

The following tests produces true for each case :

#include <iostream>
#include <string>
#include <type_traits>

template<typename T> void passByValue(T by_value)
{
std::cout << std::is_same_v<char const*, decltype(by_value)> << std::endl;
}

template<typename T> void passByReferance(T &by_ref)
{
std::cout << std::is_same_v<char const[7], std::remove_reference_t<decltype(by_ref)>> << std::endl;
}

template<typename T> void passByConstRef(const T &const_ref)
{
std::cout << std::is_same_v<char [7], std::remove_const_t<std::remove_reference_t<decltype(const_ref)>>> << std::endl;
}

int main()
{
std::cout << std::boolalpha;
passByValue("string");
passByReferance("string");
passByConstRef("string");
return 0;
}

Edit : As for std::decay, it explicitly causes array types to decay to pointers :

If T names the type "array of U" or "reference to array of U", the member typedef type is U*.

Why does const char *foo = Hello; compile but not const int *foo = 5;?

Because "Hello" is a string literal of type char [N] (in C) and const char[N] (in C++), which decays to a type char* (in C) or const char* (in C++), same as the pointer you are binding it to. On the other hand, 5 is a rvalue and its type is an int, so you cannot bind it to a pointer of type const int*.

You may find this useful.

How to make a template re-deduce a const char[N] mychar which already had decayed to const char*?

No, this is impossible. In array-to-pointer decay the type is adjusted from array of size N to pointer to. It is a many-to-one mapping that looses information about the size.

Templates can only differentiate based on types (and value categories), so the information about the array size will not be available to them anymore.

The size information may still be reproduced as the length of the contained null-terminated string (if those are assumed to be equal), e.g. in a constexpr function by searching for the pointed-to-string's null-terminator. This constant value could then be used in place of the original size of the array in a template argument or similar value-dependent type, producing again a type that differentiates on the size.

But that is not working in the way you want to call length, since there is no template argument or the like involved and you are also not returning a pointer to the beginning of the array of the string literal from pathlast.



Related Topics



Leave a reply



Submit