I don't understand why this program produces the output below.
void blah(const char* ) {printf("const char*\n");}
void blah(const std::string&) {printf("const string ref\n");}
template<class t>
void blah(t) {printf ("unknown\n");}
int main(int, char*)
{
blah("hi");
char a[4];
blah(a);
std::string s;
blah(s);
getch();
}
Outputs:
const char*
unknown
const string
In VS2008. It is willing to convert the std::string to a const reference, but why won't it convert the char* to a const char* and use the overload?
The type of "hi" is const char[3], whereas the type of a is char[4].
So, the first call requires only array-to-pointer conversion (aka "decay"). The third call requires only binding an object to a reference-to-const (I don't think "converting" is the correct terminology for reference-binding, although I may be mistaken). The second call would require array decay and a pointer conversion in order to call the const char* overload.
I claim without actually checking the overload resolution text in the standard that this extra step is what makes the template a better match than the const char* overload.
Btw, if you change "unknown\n" to "%s\n", typeid(t).name() then you can see what the type t was deduced as. For your code, it is deduced as char* (because arrays can't be passed by value), but see what happens if you change the template to take a t& parameter instead of t. Then t can be deduced as char[4].
Related
I am learning the basics of C++ and OOP in my university now. I am not 100% sure how a function pointer works when assigning functions to them. I encountered the following code:
void mystery7(int a, const double b) { cout << "mystery7" << endl; }
const int mystery8(int a, double b) { cout << "mystery8" << endl; }
int main() {
void(*p1)(int, double) = mystery7; /* No error! */
void(*p2)(int, const double) = mystery7;
const int(*p3)(int, double) = mystery8;
const int(*p4)(const int, double) = mystery8; /* No error! */
}
From my understanding, the p2 and p3 assignments are fine as the function parameters types match and const-ness is correct. But why don't the p1 and p4 assignments fail? Shouldn't it be illegal to match const double/int to non-const double/int?
According to the C++ Standard (C++ 17, 16.1 Overloadable declarations)
(3.4) — Parameter declarations that differ only in the presence or
absence of const and/or volatile are equivalent. That is, the const
and volatile type-specifiers for each parameter type are ignored when
determining which function is being declared, defined, or called.
So in the process of determining of the function type the qualifier const for example of the second parameter of the function declaration below is discarded.
void mystery7(int a, const double b);
and the function type is void( int, double ).
Also consider the following function declaration
void f( const int * const p );
It is equivalent to the following declaration
void f( const int * p );
It is the second const that makes the parameter constant (that is it declares the pointer itself as a constant object that can not be reassigned inside the function). The first const defines the type of the pointer. It is not discarded.
Pay attention to that though in the C++ Standard there is used the term "const reference" references themselves can not be constant opposite to pointers. That is the following declaration
int & const x = initializer;
is incorrect.
While this declaration
int * const x = initializer;
is correct and declares a constant pointer.
There is a special rule for function arguments passed by value.
Although const on them will affect their usage inside the function (to prevent accidents), it's basically ignored on the signature. That's because the constness of an object passed by value has no effect whatsoever on the original copied-from object at the call site.
That's what you're seeing.
(Personally I think that this design decision was a mistake; it's confusing and unnecessary! But it is what it is. Note that it comes from the same passage that silently changes void foo(T arg[5]); into void foo(T* arg);, so there's plenty of hokey bullsh!t in there already that we have to deal with!)
Do recall, though, that this doesn't just erase any const in such an argument's type. In int* const the pointer is const, but in int const* (or const int*) the pointer is non-const but is to a const thing. Only the first example relates to constness of the pointer itself and will be stripped.
[dcl.fct]/5 The type of a function is determined using the following rules. The type of each parameter (including function parameter packs) is determined from its own decl-specifier-seq and declarator. After determining the type of each parameter, any parameter of type “array of T” or of function type T is adjusted to be “pointer to T”. After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type. The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function's parameter-type-list. [ Note: This transformation does not affect the types of the parameters. For example, int(*)(const int p, decltype(p)*) and int(*)(int, const int*) are identical types. — end note ]
There is a situation where adding or removing a const qualifier to a function argument is a serious bug. It comes when you pass an argument by pointer.
Here’s a simple example of what could go wrong. This code is broken in C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// char * strncpy ( char * destination, const char * source, size_t num );
/* Undeclare the macro required by the C standard, to get a function name that
* we can assign to a pointer:
*/
#undef strncpy
// The correct declaration:
char* (*const fp1)(char*, const char*, size_t) = strncpy;
// Changing const char* to char* will give a warning:
char* (*const fp2)(char*, char*, size_t) = strncpy;
// Adding a const qualifier is actually dangerous:
char* (*const fp3)(const char*, const char*, size_t) = strncpy;
const char* const unmodifiable = "hello, world!";
int main(void)
{
// This is undefined behavior:
fp3( unmodifiable, "Whoops!", sizeof(unmodifiable) );
fputs( unmodifiable, stdout );
return EXIT_SUCCESS;
}
The problem here is with fp3. This is a pointer to a function that accepts two const char* arguments. However, it points to the standard library call strncpy()¹, whose first argument is a buffer that it modifies. That is, fp3( dest, src, length ) has a type that promises not to modify the data dest points to, but then it passes the arguments on to strncpy(), which modifies that data! This is only possible because we changed the type signature of the function.
Trying to modify a string constant is undefined behavior—we effectively told the program to call strncpy( "hello, world!", "Whoops!", sizeof("hello, world!") )—and on several different compilers I tested with, it will fail silently at runtime.
Any modern C compiler should allow the assignment to fp1 but warn you that you’re shooting yourself in the foot with either fp2 or fp3. In C++, the fp2 and fp3 lines will not compile at all without a reinterpret_cast. Adding the explicit cast makes the compiler assume you know what you’re doing and silences the warnings, but the program still fails due to its undefined behavior.
const auto fp2 =
reinterpret_cast<char*(*)(char*, char*, size_t)>(strncpy);
// Adding a const qualifier is actually dangerous:
const auto fp3 =
reinterpret_cast<char*(*)(const char*, const char*, size_t)>(strncpy);
This doesn’t arise with arguments passed by value, because the compiler makes copies of those. Marking a parameter passed by value const just means the function doesn’t expect to need to modify its temporary copy. For example, if the standard library internally declared char* strncpy( char* const dest, const char* const src, const size_t n ), it would not be able to use the K&R idiom *dest++ = *src++;. This modifies the function’s temporary copies of the arguments, which we declared const. Since this doesn’t affect the rest of the program, C doesn’t mind if you add or remove a const qualifier like that in a function prototype or function pointer. Normally, you don’t make them part of the public interface in the header file, since they’re an implementation detail.
¹ Although I use strncpy() as an example of a well-known function with the right signature, it is deprecated in general.
I am reading C++ Primer (5th Edition), 16.5, Defining a Function Template Specialization, and confused about the example given by author, let's see the following template function:
template <typename T> int compare(const T&, const T&);
and its specialization version:
template <>
int compare(const char* const &p1, const char* const &p2)
{
return strcmp(p1, p2);
}
Type of T will be const char *, but I don't think the function could be the specialization version of the template function, because I think const char* const &p1 can only be the specialization of T const & but const T &, I know I'm wrong, but I want to know why I am wrong.
Edit:
One thing to emphasize, if I invoke compare("hi", "mom"), it won't compile, that is to say, template <typename T> int compare(const T&, const T&) can not be initialized to int compare(const char* const &p1, const char* const &p2), I know T will be initialized with char[3] or char[4], but now that this won't compile, why won't compiler ignore this kind of initialization but choose one that will compile?
In C++, const T and T const mean exactly the same thing. Therefore const T & and T const & mean exactly the same thing.
It couldn't really be any other way, because references can never be changed to refer to something else (they are not "reseatable"). If you read T const & as "a reference which cannot be changed to refer to a different T", that's incorrect. It is "a reference to a T, which cannot be used to modify that T (and as with all references, cannot be changed to refer to a different T)."
const can be either before or after a type, except for a pointer type, where it must be on the right side.
Now the tricky part here is, that T in your example is set to const char*, which is a pointer type (pointer to a const char). The template says that T has to be const, and since it's a pointer type the const in the specialization has to be put after the type, hence you get const char* const.
It becomes a bit more clear when reading it aloud from right to left:
"a const pointer to a char that is const"
//Edit:
Why can't you call compare("hi", "mom");? Because those char arrays are treated as different types by your compiler (char[3] and char[4]), but the template specifies both parameters to be the same type.
This will match the template, however it will not match your specialization (as T is now char[2]):
compare("a", "b");
This works and uses your specialized method:
const char * hi = "hi";
const char * mom = "mom";
compare(hi, mom);
//Edit2:
"I know T will be initialized with char[3] or char[4], [...] why won't compiler ignore this kind of initialization but choose one that will compile?"
Because C++ is a strongly typed language. The compiler doesn't do guess work for you, it takes the types at face value. If they don't match, they don't match. It's your job as developer to do it right.
I have below function (Just for reproducing the issue):
template <typename KeyT>
void func(const KeyT cptr) {
std::cout << typeid(KeyT).name() << std::endl;
}
I would like to call this with a string literal, like below:
func<char*>("literal");
But, I end up getting below warning:
warning: ISO C++11 does not allow conversion from string literal to 'char *' [-Wc++11-compat-deprecated-writable-strings]
I have a specific need to use char* as my Key type and I was expecting TAD to consider the param type as const char* in whole as I am not taking it by reference.
The warning comes with both clang and g++ compilers.
How is the param type being deduced here ?
Thanks in advance.
I was expecting TAD to consider the param type as const char* ...
How is the param type being deduced here ?
template <typename KeyT>
void func(const KeyT cptr)
Note that const is the qualifier on KeyT itself, it means if KeyT is a pointer, then cptr will be a const pointer, not a pointer to const.
"literal" is sring literal with type const char[8], which might decay to const char*. Then KeyT might be deduced as const char*, then the type of cptr will be const char* const.
You're specifying the template argument type as char*, then makes cptr a char* const. But from C++11, converting a string literal to char* implicitly is not allowed since literals are const.
Why do you need the template parameter type to be char*? To modify it inside the function? Note that modifying a string literal will lead to UB. You could pass a char array to it, like:
char my_chararray[] = "literal";
func<char*>(my_chararray); // or just func(my_chararray);
There is no deduction here: you have explicitly stated that the template parameter is char*.
What you're missing is that parameter substitution is not a text search-and-replace: it actually follows the logical rules for the type system. const KeyT cptr declares a const instance of the type KeyT — when KeyT is char*, the parameter declaration becomes char *const cptr.
Is it legal to define two class functions, one overloaded with a reference to a string and the other overloaded with a const char*?
void funcA(const std::string& s)
void funcA(const char* s)
Can I guarantee that if I call funcA() with a const char* input, it will not call the string function? I'm asking because there is an implicit construction from a const char* to a string.
Yes, it's valid. If you pass a const char*, the second overload is an exact match, which is preferred over all other overloads, particularly ones involving user-defined conversions (such as converting to a const std::string).
From C++ Primer, I know that for template's parameter arguments, only two kinds of conversions are performed: one is const conversion, another one is array/function to pointer conversion.
However, when it comes to explicit argument, it seems that everything changes.
Assume we have a template function:
template <typename T>
int compare(const T &a, const T &b)
{
// do comparison
}
If no explicit argument involved, function call like this is illegal:
compare("foo", "foobar");
The weird thing happens (actually, it might not be weird but I do not understand) when we explicitly do:
compare<std::string>("foo", "foobar");
It seems that in the second call, "foo" and "foobar" are converted to std::string, which is controversial.
Is there any special rules for template explicit arguments? Thanks.
In the first case the compiler tries to deduce the type T from the given parameters. From the first parameter the compiler deduces the type const char (&)[4] (aka. reference to an array of 4 characters), from the second it gets const char (&)[7]. The two types don't match and the compiler can't figure out what T should be.
In the second example you explicitly specify that the T template parameter should be std::string. So T will be std::string. The compiler accepts the type you give and checks if the given function parameters match that type. In this case the parameters fit, because "foo" and "foobar" can be implicitly converted to std::string. (The const char[] degrade to const char*, and then there is a constructor that can construct a std::string from a const char*)