I have a function that I want to work for const char*'s but it only works for string literals because they are given a special rule to be allowed to initialize arrays. The second overload, foo(const char*) will be preferred for both string literals and const char*s, but my template overload will not work for const char*s.
// Errors for const char*.
template<typename T, size_t n>
void foo(const T (&s)[n])
{
}
// Selected both times if both overloads are present.
void foo(const char*)
{
}
int main()
{
foo("hello");
const char* s = "hello";
foo(s); // Error if foo(const char*) is absent.
}
Is there a way to allow const char*s to initialize arrays?
I've tried this:
template<typename T, size_t n>
void _foo(const T (&s)[n])
{
std::cout << __PRETTY_FUNCTION__;
}
#define _expand(s) #s
#define expand(s) _expand(s)
void foo(const char* s)
{
_foo(expand(s));
}
I think that
const char* s = "hello";
is a pointer to the string literal somewhere in read only memory, and compiler cannot deduce the array size, so it chooses second overload.
You can use
const char s[] = "hello";
No, you can't initialize an array with a pointer. The string literal syntax is a special shorthand for a const char s[] and your working code roughly equivalent to
static const char s[]{'h','e','l','l','o','\0'};
foo(s);
So, you can pass arrays to your template function, including string literals, but you cannot pass pointers, including pointers to string literals.
On the other hand, arrays can decay to pointers, which is why both arrays and pointers can be passed to your second overload.
Related
I know :
const char* foo = "ab"; // OK.There is a "array to pointer" decay
const char a[3] = "ab"; // OK.
const char a[2] = "ab"; // Error. "ab" is const char [3]
Then why not also distinguishing it when use const char a[N], const char * as a function parameter?
Function declaration void foo(const char *a) and void foo(const char a[3]) are considered to be the same. There is no restrictions like const char a[2] = "ab"; when I call foo("abcdefg"); The inconsistent(unsymmetrical) behavior makes me a little bit curious in the very first moment.
On cppreference we can find
The type of each function parameter in the parameter list is determined according to the following rules:
...
2) If the type is "array of T" or "array of unknown bound of T", it is replaced by the type "pointer to T"
As to why, i believe it was simply adopted from C like many other language features.
Because that's how the language is specified to work.
If you explicitly want an array as argument you have to use templates and references:
template<size_t N>
void foo1(char (&a)[N]);
Or use std::array instead of plain C-style arrays:
void foo2(std::array<char, 3> a);
Or if you want to be able to use either std::array or std::vector then back to templates:
template<typename C>
void foo3(C a);
The last can take any container-type that have the same interface.
Example using the above functions:
char arr1[3] = "ab";
foo1(arr1); // Array will *not* decay to a pointer
std::array<char, 3> arr2 = {{ 'a', 'b', 0 }};
foo2(arr2);
std::vector<char> vec1 = { 'a', 'b', 0 };
foo3(vec1); // Can pass std::vector
std::string str1 = "ab";
foo3(str1); // Can pass std::string
foo3(arr2); // Can pass std::array
What I want to achieve is to have overloads of a function that work for string literals and std::string, but produce a compile time error for const char* parameters. The following code does almost what I want:
#include <iostream>
#include <string>
void foo(const char *& str) = delete;
void foo(const std::string& str) {
std::cout << "In overload for const std::string& : " << str << std::endl;
}
template<size_t N>
void foo(const char (& str)[N]) {
std::cout << "In overload for array with " << N << " elements : " << str << std::endl;
}
int main() {
const char* ptr = "ptr to const";
const char* const c_ptr = "const ptr to const";
const char arr[] = "const array";
std::string cppStr = "cpp string";
foo("String literal");
//foo(ptr); //<- compile time error
foo(c_ptr); //<- this should produce an error
foo(arr); //<- this ideally should also produce an error
foo(cppStr);
}
I'm not happy, that it compiles for the char array variable, but I think there is no way around it if I want to accept string literals (if there is, please tell me)
What I would like to avoid however, is that the std::string overload accepts const char * const variables. Unfortunately, I can't just declare a deleted overload that takes a const char * const& parameter, because that would also match the string literal.
Any idea, how I can make foo(c_ptr) produce a compile-time error without affecting the other overloads?
This code does what is needed (except the array - literals are arrays, so you can't separate them)
#include <cstddef>
#include <string>
template <class T>
void foo(const T* const & str) = delete;
void foo(const std::string& str);
template<std::size_t N>
void foo(const char (& str)[N]);
int main() {
const char* ptr = "ptr to const";
const char* const c_ptr = "const ptr to const";
const char arr[] = "const array";
std::string cppStr = "cpp string";
foo("String literal");
//foo(ptr); //<- compile time error
// foo(c_ptr); //<- this should produce an error
foo(arr); //<- this ideally should also produce an error
foo(cppStr);
}
In order for your deleted function to not be a better match than the template function, so that string literals still work, the deleted function needs to also be a template. This seems to satisfy your requirements (though the array is still allowed):
template <typename T>
typename std::enable_if<std::is_same<std::decay_t<T>, const char*>::value>::type
foo(T&& str) = delete;
Demo.
In modern versions of the language you may create some custom type and user-defined literal that will create it, so that it will be possible to pass "this"_SOMEWORDS, but not just c string literal, chat pointer or char array.
It doesn't exactly satisfy your requirements to pass string literal but I think it's good enough, especially because it forbids also arrays
A function returning a copy of an integer literal
int number()
{ return 1; }
can be easily converted to a plain compile-time expression using the keyword constexpr.
constexpr int number()
{ return 1; }
However, I'm getting confused when it comes to string literals. The usual method is returning a pointer to const char that points to the string literal,
const char* hello()
{ return "hello world"; }
but I think that merely changing "const" to constexpr is not what I want (as a bonus, it also produces the compiler warning deprecated conversion from string constant to 'char*' using gcc 4.7.1)
constexpr char* hello()
{ return "hello world"; }
Is there a way to implement hello() in such a way that the call is substituted with a constant expression in the example below?
int main()
{
std::cout << hello() << "\n";
return 0;
}
const and constexpr are not interchangeable, in your case you do not want to drop the const but you want to add constexpr like so:
constexpr const char* hello()
{
return "hello world";
}
The warning you receive when you drop const, is because a string literal is an array of n const char and so a pointer to a string literal should be a *const char ** but in C a string literal is an array of char even though it is undefined behavior to attempt to modify them it was kept around for backwards compatibility but is depreciated so it should be avoided.
Force constexpr evaluation:
constexpr const char * hi = hello();
std::cout << hi << std::endl;
I have a function:
void add(char const**);
And I invoke it as follow:
template<typename T>
void anotherAdd(T const& t) {
add(&t);
}
...
anotherAdd("some string");
As a result I get the error:
no known conversion for argument 1 from 'const char (*)[10]' to 'const char**'
Why the conversion can't be made?
Because I think the following is true:
"some string" <=> char const* =>
&"some string" <=> char const**
Arrays are not pointers.
This code expects a pointer to a pointer
void add(char const**);
The compiler is telling you that it can't produce a pointer to a pointer because your code has no pointer to point to. You're effectively trying to evaluate &"some string", which has no valid meaning.
This code will work, because it creates the missing char const* that you're trying to take the address of.
template<typename T>
void anotherAdd(T const& t) {
char const *pt = &t; // Now there's a pointer that you can take the address of.
add(&pt);
}
In C++, these two have no difference.
char *pC;
char* pC2;
I thought that the type char and char* have got to be different types and all the C++ programmers had to use this form char* p instead of char *p, because when you do char *p it doesn't seem to specify that the programmer is using a pointer to char.
However, when you take a look at this code below, which is the same as nullptr from C++0x, it seems that T doesn't recognize any pointer type.
const class{
public:
// T is char, not char* in this case.
// T* is char*, but I thought it would be char**
template <class T>
operator T*() const{
cout << "Operator T*() Called" << endl;
return 0;
}
template <class C, class T>
operator T C::*() const{
cout << "Operator C T::*() Called" << endl;
return 0;
}
private:
void operator&() const;
}AAA = {};
int main(){
// operator T*() const is called,
// but it's awkward,
// because it looks like T will be char* type, so T* will be char**
char* pC = AAA;
}
Thanks in advance!
You're correct that char and char* are different types. Whether programmers say char* p or char *p is irrelevant. Those are three separate tokens, and there can be as many or as few spaces between them as you want; it has no effect on the type of p.
When you initialize pC with AAA, the compiler needs to choose a conversion operator from AAA. Since it's initializing a char* variable, it wants operator char*. To make that work, the compiler needs to choose T = char. Since T is char, there's no way T* could be char**.
If you want T to be a pointer type, then use AAA in a context where a pointer-to-pointer type is expected, such as char**. Then T will be char*.
Writing char* p or char *p is just the same thing, both are pointers to a char.