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.
Related
Consider this pseudo code for a type deduction case:
template<typename T> void f(ParamType param);
Call to function will be:f(expr);
According to type deduction case where ParamType is not a reference, pointer, nor a universal reference
(see S. Meyers "Effective Modern C++", p.14), but passed by value, to determine type T, one needs firstly
to ignore the reference and const part of 'expr' and then pattern-match exprs type to determine T.
The driver will be:
void PerformTest() {
int i = 42;
int* pI = &i;
f_const_left(pI);
f_non_template_left(pI);
f_const_right(pI);
f_non_template_right(pI);
}
Now consider these functions, which, using this deduction rule, are showing some counter-intuitive results while being called with pointer as an argument:
template<typename T> void f_const_left(const T t) {
// If 'expr' is 'int *' then, according to deduction rule for value parameter (Meyers p. 14),
// we need to get rid of '&' and 'const' in exp (if they exist) to determine T, thus T will be 'int *'.
// Hence, ParamType will be 'const int *'.
// From this it follows that:
// 1. This function is equivalent to function 'func(const int * t){}'
// 2. If ParamType is 'const int *' then we have non-const pointer to a const object,
// which means that we can change what pointer points to but cant change the value
// of pointer address using operator '*'
*t = 123;// compiler shows no error which is contradiction to ParamType being 'const int *'
t = nullptr; // compiler shows error that we cant assign to a variable that is const
// As we see, consequence 2. is not satisfied:
// T is straight opposite: instead of being 'const int *'
// T is 'int const *'.
// So, the question is:
// Why T is not 'const int*' if template function is f(const T t) for expr 'int *' ?
}
Consider consequence 1.:
Lets create an equivalent non-template function:
void f_non_template_left(const int* t) {
// 1. Can we change the value through pointer?
*t = 123; // ERROR: expression must be a modifiable lvalue
// 2. Can we change what pointers points to?
t = nullptr; // NO ERROR
// As we can see, with non-template function situation is quite opposite.
}
For for completeness of the experiment, lets also consider another pair of functions but with 'const' being placed from the right side of a T: one template function and its non-template equivalent:
template<typename T> void f_const_right(T const t) {
// For expr being 'int *' T will be 'int *' and ParamType will be 'int * const',
// which is definition of a constant pointer, which cant point to another address,
// but can be used to change value through '*' operator.
// Lets check it:
// Cant point to another address:
t = nullptr; // compiler shows error that we cant assign to a variable that is const
// Can be used to change its value:
*t = 123;
// So, as we see, in case of 'T const t' we get 'int * const' which is constant pointer, which
// is intuitive.
}
Finally, the non-template function with 'const' from the right side of type:
void f_non_template_right(int* const t) {
// 1. Can we change the value through pointer?
*t = 123; // No errors
// 2. Can we change what pointers points to?
t = nullptr; // ERROR: you cant assign to a variable that is const
// As we can see, this non-template function is equivalent to its template prototype
}
Can someone explain why there is such insonsistency between template and non-template functions ?
And why template function with 'const' on the left is behaving not according to the rule of deduction?
// Hence, ParamType will be 'const int *'.
No, it will not (and not just because ParamType is not used in the code. For the purposes of this answer, let's pretend you meant T, which is there).
int * is a sequence of two tokens. One of those tokens is a typename (int), and the other is *. When combined in this way, the two tokens name a single type: pointer to an int.
const int is a sequence of two tokens. When combined, they name the type: const int.
const int* is a sequence of 3 tokens. When combined as such, they name a single type. The rules of C++ type naming are such that the const applies "const-ness" to the type specified by whatever is to the immediate left of it. If nothing is to its left, it applies to what is immediately to the right. So if you consider this as an expression, it is read as (const int)*. Therefore, this token sequence names the type: pointer to a const int.
const T, where T is a typename (which could be a template parameter, a type-alias, or the name of a type), is a sequence of two tokens. When combined, they name a single type: const (whatever type T designates).
If the type T happens to name is int*, then the type T designates is 'pointer to int', as previously discussed. Therefore, const T designates 'const (pointer to int)' Note that the pointer is what is const, not the int being pointed to.
Substitution of type aliases and template parameters is not done by copying-and-pasting tokens. It is done by applying the rules of typenames to the alias as a single unit. Whatever T is, the qualifiers like constapply to T as a unit.
int * const is a sequence of three tokens. Per the aforementioned rules, const is applied to whatever is to its left, if anything. So const applies to the *. Therefore, these tokens name the type: const pointer to int.
T const is a sequence of two tokens. When combined as such, they name a single type: const (whatever type T designates).
This is why T const and int * const behave the same, but const T and const int * don't.
(Referencing the C++14 Standard)
Your f_non_template_* functions aren't entirely correct.
Since T is a template parameter, it'll behave as if it is a unique type:
14.5.6.2 Partial ordering of function templates
(3) To produce the transformed template, for each type, non-type, or template template parameter (including
template parameter packs (14.5.3) thereof) synthesize a unique type, value, or class template respectively
and substitute it for each occurrence of that parameter in the function type of the template.
So to correctly test this your non-template functions would need to defined like this:
using TT = int*;
void f_non_template_left(const TT t) {
/* ... */
}
void f_non_template_right(TT const t) {
/* ... */
}
godbolt example
at which point you'll get exactly the same behaviour as with the templated functions.
Why it works that way
In this case T would be deduced to int*, which as a unique type would be a compound type:
3.9.2 Compound types
(1) Compound types can be constructed in the following ways:
[...]
(1.3) — pointers to void or objects or functions (including static members of classes) of a given type
[...]
And the cv-rules for compound types are as following:
3.9.3 CV-qualifiers
(1) A type mentioned in 3.9.1 and 3.9.2 [Compound Type] is a cv-unqualified type. Each type which is a cv-unqualified complete
or incomplete object type or is void (3.9) has three corresponding cv-qualified versions of its type: a constqualified version, a volatile-qualified version, and a const-volatile-qualified version.
(2) A compound type (3.9.2) is not cv-qualified by the cv-qualifiers (if any) of the types from which it is compounded. Any cv-qualifiers applied to an array type affect the array element type, not the array type (8.3.4).
So your cv-qualifiers for T in your template function refer to the top-level constness of the compound-type T in both your cases, so
template<typename T> void f_const_left(const T t);
template<typename T> void f_const_right(T const t);
are actually equivalent.
The only exception to this would be if T would be an array-type, in which case the cv-qualifier would apply to the elements of the array instead.
If you want to specify the constness of the pointed-to value, you could do it like this:
//const value
template<class T>
void fn(const T* value);
// const pointer
template<class T>
void fn(T* const value);
// const value + const pointer
template<class T>
void fn(const T* const value);
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 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].
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*)