Behavior of function overloading with const - c++

I'm working on g++ and here I have tried to overload a function by just adding const to parameter. It works fine and when it runs, it calls the function without const
Is this behavior specified in the C++ standard?
What the reason it calls the function without const
void print(const std::string& str){std::cout << "const" << str << std::endl;}
void print(std::string& str){std::cout << str << std::endl;}
int main()
{
std::string temp = "hello";
print(temp);
return 0;
}

Reference bindings are an identity category §13.3.3.1.4) but since the latter is more cv-qualified, for §13.3.3.2, the non-const is preferred (sample code from the standard):
int f(const int &);
int f(int &);
int i;
int j = f(i); // calls f(int &)

That is standard behavior. Any other behavior would lead to crazy behavior. In particular, the non-const function would not be callable at all.

const is part of method signature. Overriding works only for methods with the same signature.
This behavior was made to avoid reverse situation when you use const method of base class to call not const method of child class.

The reason is this section in [over.ics.rank]/3 where this case is explicitly covered:
Standard conversion sequence S1 is a better conversion sequence than
standard conversion sequence S2 if […] — S1 and S2 are reference
bindings (8.5.3), and the types to which the references refer are the
same type except for top-level cv-qualifiers, and the type to which
the reference initialized by S2 refers is more cv-qualified than the
type to which the reference initialized by S1 refers.
S1 corresponds to the second overload and S2 to the first.
What the reason it calls the function without const
You always try to select the most specialized thing. That is the case in overload resolution just as it is in partial ordering of function templates. The second overload is more specialized than the first because the first can be called with arguments which the second cannot be called with - that is the basic reasoning behind this rule.

Overloading works by matching the types of the arguments, including the qualifiers. In your case temp has type std::string not const std::string. You have only initialised it with a literal constant, it is not itself constant.
Consider the following:
std::string temp( "hello" ) ;
print(temp); // hello
print( std::string("hello") ) ; // consthello
print( "hello" ) ; // consthello
print( static_cast<const std::string>(temp) ) ; // consthello
const std::string temp2( "hello" ) ;
print(temp2); // consthello
If you were to remove the non-const version, all three will call the remaining const overload. In this example, only the const version is in fact necessary (and preferred) since neither version modify the string object.
If on the other hand you removed the non-const version, there would be no function matching any but the first example above, and the build would fail. That is to say a non-const object can safely be passed as a const argument, but a const object cannot be passed as a non-const argument, because the function is not "promising" not to modify the object. You can force a const into a non-const argument by a const_cast as in:
const std::string temp2("hello") ;
print( const_cast<std::string&>(temp2) ) ; // hello
But if print() were to attempt to modify the object in this case the results are undefined, so consider the practice unsafe.
Making an argument const indicates intent, allows the compiler to issue a diagnostic if the code is attempts to modify the object or pass it via a non-const argument to some other function. It may also potentially provide the compiler with optimisation possibilities.

Because calling the function taking std::string const& requires two implicit conversions: one to std::string const, one to std::string const&; whereas calling the function taking std::string& requires merely one implicit conversion (to std::string&), so that one is preferred.

Related

Understanding comment from the errata about Item 41 of EMC++

In Item 41, Scott Meyers writes the following two classes:
class Widget {
public:
void addName(const std::string& newName) // take lvalue;
{ names.push_back(newName); } // copy it
void addName(std::string&& newName) // take rvalue;
{ names.push_back(std::move(newName)); } // move it; ...
private:
std::vector<std::string> names;
};
class Widget {
public:
template<typename T> // take lvalues
void addName(T&& newName) // and rvalues;
{ // copy lvalues,
names.push_back(std::forward<T>(newName)); } // move rvalues;
} // ...
private:
std::vector<std::string> names;
};
What's written in the comments is correct, even if it doesn't mean at all that the two solutions are equivalent, and some of the differences are indeed discussed in the book.
In the errata, however, the author comments another difference not discussed in the book:
Another behavioral difference between (1) overloading for lvalues and rvalues and (2) a template taking a universal reference (uref) is that the lvalue overload declares its parameter const, while the uref approach doesn't. This means that functions invoked on the lvalue overload's parameter will always be the const versions, while functions invoked on the uref version's parameter will be the const versions only if the argument passed in is const. In other words, non-const lvalue arguments may yield different behavior in the overloading design vis-a-vis the uref design.
But I'm not sure I understand it.
Actually, writing this question I've probably understood, but I'm not writing an answer as I'm still not sure.
Probably the author is saying that when a non-const lvalue is passed to addName, newName is const in the first code, and non-const in the second code, which means that if newName was passed to another function (or a member function was called on it), than that function would be required to take a const parameter (or be a const member function).
Have I interpreted correctly?
However, I don't see how this makes a difference in the specific example, since no member function is called on newName, nor it is passed to a function which has different overloads for const and non-const parameters (not exactly: std::vector<T>::push_back has two overloads for const T& arguments and T&& arguments`, but an lvalue would still bind only to the former overload...).
In the second scenario, when a const std::string lvalue is passed to the template
template<typename T>
void addName(T&& newName)
{ names.push_back(std::forward<T>(newName)); }
the instantiation results in the following (where I've removed the std::forward call as it is, in practice, a no-op)
void addName(const std::string& newName)
{ names.push_back(newName); }
whereas if a std::string lvalue is passed, then the resulting instance of addName is
void addName(std::string& newName)
{ names.push_back(newName); }
which means that the non-const version of std::vector<>::push_back is called.
In the first scenario, when a std::string lvalue is passed to addName, the first overload is picked regardless of the const-ness
void addName(const std::string& newName)
{ names.push_back(newName); }
which means that the const overload of std::vector<>::push_back is selected in both cases.

Assigning function to function pointer, const argument correctness?

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.

c++11 - initialize std::string from char* directly with {} constructor

I have a function which accepts a std::string&:
void f(std::string& s) { ... }
I have a const char* which should be the input parameter for that function. This works:
const char* s1 = "test";
std::string s2{s};
f(s2);
This doesn't:
const char* s1 = "test";
f({s1});
Why isn't this possible? The funny thing is that CLion IDE is not complaining, but the compiler is:
no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘std::basic_string<char>&’
This has nothing to do with constructing std::string from char const*.
f expects a lvalue to a string, and by creating a temporary instance on the spot, you're providing an rvalue, which cannot be bound to a non-const lvalue reference. f(string{}) is just as invalid.
Your function receives a non const reference and you are passing a temporary object, which requires a copy or a const reference parameter. Two solutions, creating another function to receive the object as a rvalue reference and call the other overload within
void f(string&& s) { f(s); }
to allow temporary objects as parameter, or change your function definition to receive any object but as a constant reference
void f(const std::string& s) { ... }
One option is to change your function to take a string by value, not by reference. Then it will work. In any case, in C++11 sometimes it's preferable to pass by value, not by reference.

Why rvalue reference argument matches to const reference in overload resolution?

Potentially related articles:
Overload resolution between object, rvalue reference, const reference
std::begin and R-values
For a STL container C, std::begin(C) and similar access functions including std::data(C) (since C++17) are supposed to have the same behavior of C::begin() and the other corresponding C's methods. However, I am observing some interesting behaviors due to the details of overload resolution involving lvalue/rvalue references and constness.
DataType1 is int* as easily expected. Also, confirmed the by with Boost's type_id_with_cvr. const vector<int> gives int const* No surprise here.
using T = vector<int>;
using DataType1 = decltype(T().data()); // int*
using CT = const T;
using DataType2 = decltype(CT().data()); // int const*
using boost::typeindex::type_id_with_cvr;
cout << type_id_with_cvr<DataType1>() << endl; // prints int*
...
I tried std::data, which can also handle arrays and non-STL containers. But it yields int const*. Similarly, std::begin returns a const iterator, even though T is not const.
using T = vector<int>;
using DataType3 = decltype(std::data(T())); // int const* Why ???
using CT = const T;
using DataType4 = decltype(std::data(CT())); // int const*
Question: What is the reason of this difference? I expected that C.data() and std::data(C) would behave in the same manner.
Some my research: In order to get int* for DataType3, T must be converted to non-const lvalue reference type explicitly. I tried declval, and it was working.
using DataType3 = decltype(std::data(std::declval<T&>())); // int*
std::data provides two overloads:
template <class _Cont> constexpr
auto data(_Cont& __c) -> decltype(__c.data()) { return __c.data(); }
// non-const rvalue reference argument matches to this version.
template <class _Cont> constexpr
auto data(const _Cont& __c) -> decltype(__c.data()) { return __c.data(); }
While resolving overloaded functions for std::data, T(), which is non-const rvalue reference, is matched to the const T& version instead of T& version.
It was not easy to find this specific overload resolution rule in the standard (13.3, over.match). It'd be much clearer if someone could point the exact rules for this issue.
This behaviour is attributed to overload resolution rules. As per standard 8.5.3/p5.2 References [dcl.init.ref], rvalue references bind to const lvalue references. In this example:
std::data(T())
You provide to std::data an rvalue. Thus, due to overload resolution rules the overload:
template <class _Cont> constexpr
auto data(const _Cont& __c) -> decltype(__c.data()) { return __c.data(); }
is a better match. Consequently you get const int*
You can't bind a temporary to a non-const lvalue reference.
The only line that is mildly surprising is using DataType1 = decltype(T().data()); // int*.
...but it is still normal, member functions can be called on temporary objects without being treated as const. This is another non trivial difference between member functions and free functions.
For example, in C++98 (pre-rvalue refs) it was not possible to do std::ofstream("file.txt") << std::string("text") because operator<< is not member and the temporary is treated as const. If operator<< were a member of std::ofstream it would be possible (and may even make sense). (The situation changed later in C++11 with rvalue references but the point is still valid).
Here it is an example:
#include<iostream>
struct A{
void f() const{std::cout << "const member" << std::endl;}
void f(){std::cout << "non const member" << std::endl;}
};
void f(A const&){std::cout << "const function" << std::endl;}
void f(A&){std::cout << "non const function" << std::endl;}
int main(){
A().f(); // prints "non const member"
f(A()); // prints "const function"
}
The behavior is exposed when the object is temporarily constructed and used. In all other cases I can imagine, member f is equivalent to f free function.
(r-value reference qualifications && --for member and function-- can give you a more fine grained control but it was not part of the question.)

passing a const char instead of a std::string as function argument

This is a newbie question but I cannot understand how it works.
Suppose I have the function like the one below
void foo(const std::string& v) {
cout << v << endl;
}
And the call below in my program.
foo("hi!");
Essentially I am passing a const char* to a function argument that is const reference to a string so I have a doubt on this call.
In order to pass an argument by reference, am I right to say that the variable must exist at least for the duration of the call? If it is so, where is created the string that is passed to the function?
I can see that it works : does it happen because the compiler creates a temporary string that is passed to the argument or the function?
does it happen because the compiler creates a temporary string that is passed to the argument or the function?
Yes, and temporaries are allowed to bind to const lvalue references. The temporary string v is alive for the duration of the function call.
Note that this is possible because std::string has a implicit converting constructor with a const char* parameter. It is the same constructor that makes this possible:
std::string s = "foo";