I am trying to understand the rules of c++ automatic and explicit conversions in regular or member function calls. I wrote the following code and it fails compilation:
#include <iostream>
#include <string>
using namespace std;
class testExplicit {
public:
int intval;
short shortval;
double doubleval;
char charval;
string strval;
testExplicit(int a1, short a2, double a3, char a4, string& a5):
intval(a1),shortval(a2),doubleval(a3),charval(a4),strval(a5){}
void getVal(int& a) { a = intval; cout << "IntVal\n"; }
// void getVal(short& a) { a = shortval; cout << "Short Val\n"; }
// void getVal(double& a) { a = doubleval; cout << "Double Val\n"; }
// void getVal(char& a) { a = charval; cout << "Char Val\n"; }
// void getVal(string& a) { a = strval; cout << "String Val\n"; }
};
int main( int argc, char **argv ) {
string s ("test Str");
testExplicit test (100,10,10.05,5,s);
int i;
char c;
double d;
short f;
test.getVal(i);
test.getVal(c);
test.getVal(d);
test.getVal(f);
return 0;
}
However, can I conclude that the functions only expect the exact matching parameter? I remember reading that automatic conversions happen according to the conversion rules. Can some shed some light on the correct rules pls?
Here is the error:
test.cpp: In function 'int main(int, char**)':
test.cpp:38: error: no matching function for call to 'testExplicit::getVal(char&)'
test.cpp:17: note: candidates are: void testExplicit::getVal(int&)
test.cpp:39: error: no matching function for call to 'testExplicit::getVal(double&)'
test.cpp:17: note: candidates are: void testExplicit::getVal(int&)
test.cpp:40: error: no matching function for call to 'testExplicit::getVal(short int&)'
test.cpp:17: note: candidates are: void testExplicit::getVal(int&)
Thanks
void getVal(int& a);
This function takes an int by reference. You must pass it an int. This is similar to how if you had a function
void getVal(int* a);
you would need to pass it a pointer to an int (not a pointer to a short or any other type).
One reason for this is that you are able to modify the int from within the function. In order to allow you to pass an object of another type (e.g. a short) to this function, a temporary object of type short would have to be created at runtime and a reference to that temporary object would have to be passed.
This wouldn't be ideal because you might accidentally end up passing the wrong type of object (e.g. a short) and expecting it to be modified by the function, when in fact a temporary copy of type int would be modified by the function, not the original short.
You are, however, permitted to bind const references to temporary objects, so if your function was declared as
void getVal(const int& a);
you would be able to pass it any type that is convertible to int. This makes some sense, since the function cannot modify the referenced object (because it is a const reference), so the "oops, I'm accidentally modifying a temporary object" problem doesn't exist.
Conversions can also take place when you pass by value, but this too makes sense: when you pass by value, a copy has to be made anyway (the copy that is passed to the function by value), so the conversion can take place as part of that copy.
The problem is that if an implicit conversion is required, then a temporary variable is created. You cannot have a non-const reference to a temporary variable, so you would need to make your member function void getVal(const int &a) or void getVal(int a).
Implicit conversion should be avoided. C++ is a strongly typed language it is far better to create functions for the types you wish to accept, use templates, or explicitly cast your type to another type, etc.
Constructor casting or using static_cast<>, dynamic_cast<>, or reinterpret_cast<> are some of the options depending on the situation when you need to convert types from one to another.
Templates can allow you to handle multiple types a little easier.
template<typename T>
void getVal(const T& a)
{
// do something
}
Implicit conversions are done by creating an rvalue (temporary), and rvalues cannot be bound to non-const references. That is the reason why it is not working. If, on the other hand you change the signature to take the arguments either by value or by constant reference, then it will work:
void f( int a ) { std::cout << a << std::endl; }
void g( const int& b ) { std::cout << b << std::endl; }
int main() {
char c = 'a'; // note that this the ASCII value of 'a'
f( c );
g( c );
short s = 4;
f( s );
g( s );
}
Pass by reference means, creating an alias for the same type. So try changing void getVal(int& a) to void getVal(int a), to work for the other function calls differing the passing parameter type
Related
Does declaring something like the following
void foo(int x) { std::cout << "foo(int)" << std::endl; }
void foo(const int &x) { std::cout << "foo(const int &)" << std::endl; }
ever make sense? How would the caller be able to differentiate between them? I've tried
foo(9); // Compiler complains ambiguous call.
int x = 9;
foo(x); // Also ambiguous.
const int &y = x;
foo(y); // Also ambiguous.
The intent seems to be to differenciate between invocations with temporaries (i.e. 9) and 'regular' argument passing. The first case may allow the function implementation to employ optimizations since it is clear that the arguments will be disposed afterwards (which is absolutely senseless for integer literals, but may make sense for user-defined objects).
However, the current C++ language standard does not offer a way to overload specifically for the 'l/r-valueness' of arguments - any l-value being passed as argument to a function can be implicitly converted to a reference, so the ambiguity is unavoidable.
C++11 introduces a new tool for a similar purpose — using r-value references, you can overload as follows
void foo(int x) { ... }
void foo(const int &&x) { ... }
... and foo(4) (a temporary, r-value passed as argument) would cause the compiler to pick the second overload while int i = 2; foo(i) would pick the first.
(note: even with the new toolchain, it is not possible to differentiate between the cases 2 and 3 in your sample!)
You could do this with a template:
template<typename T> void foo(T x) { ... }
Then you can call this template by value or by reference:
int x = 123;
foo<int>(x); // by value
foo<int const&>(x); // by refernce
How would the caller be able to differentiate between them?
It cannot be differentiated in this case. Both the overloaded functions have the same type of primitive data type as the argument. And taking by reference doesn't count for a different type.
You can use static_cast to explicitly select the overload to be called:
#include <iostream>
void foo(int x) { std::cout << "foo(int)" << std::endl; }
void foo(const int &x) { std::cout << "foo(const int &)" << std::endl; }
int main()
{
int x = 0;
auto f1 = static_cast< void(*)(int) >(foo);
f1(x);
auto f2 = static_cast< void(*)(const int&) >(foo);
f2(x);
}
However, you should ask yourself why you provided those two overloads in the first place. Either you are fine with making a copy or you are not. Both at the same time? Why? Also making it necessary for the caller to explicitly select the overload defeats the purpse of function overloading. If you really want that consider to supply two functions instead:
void foo_copying(int x) { std::cout << "foo(int)" << std::endl; }
void foo_non_copying(const int &x) { std::cout << "foo(const int &)" << std::endl; }
Not in C++. Functional languages such as Erlang and Haskell get closer by allowing you to specify function overloads based on parameter value, but most imperative languages including C++ require overloading based on method signature; that is, the number and type of each parameter and the type of the return value.
The const keyword in the signature defines not the type of the parameter, but its mutability within the function; a "const" parameter will generate a compiler error if modified by the function or passed by reference to any function that doesn't also use const.
The compiler can't.
Both definitions of foo can be used for all 'variants' of int.
In the first foo, a copy of the int is made. Copying an int is always possible.
In the second foo, a reference to a const int is passed. Since any int can be cast to a const int, a reference to it can be passed as well.
Since both variants are valid in all cases, the compiler can't choose.
Things become different if you e.g. use the following definition:
void foo (int &x);
Now calling it with foo(9) will take the first alternative, since you can't pass 9 as a non-const int reference.
Another example, if you replace int by a class where the copy constructor is private, then the caller can't make a copy of the value, and the first foo-variant will not be used.
Consider the following piece of code:
#include <iostream>
using namespace std;
class A {
private:
int x;
public:
int& get_ref() {
cerr << "non const" << endl;
return x;
}
const int& get_ref() const {
cerr << "const" << endl;
return x;
}
};
int main () {
A a;
a.get_ref() = 10;
cout << a.get_ref() << endl;
const int& y = a.get_ref();
return 0;
}
I expect the second and third calls to a.get_ref() to run the second version of get_ref() method (and output const on the standard error). But it looks like always the first version is called. How can I implement two different 'getter's and make sure that the proper version is called based on the context? I.e., at least for the third call
const int& y = a.get_ref();
the second version is executed? (A non-elegant solution would be to use different names e.g. get_ref and get_const_ref but I am trying to see if that can be avoided.)
Overload resolution doesn't depend on return values, but only arguments, including the object to be called on for member function. a is a non-const object, then for a.get_ref(), the non-const member function will always be called.
You can cast it to const for the const version to be called:
const_cast<const A&>(a).get_ref();
BTW: Giving them different names is not a bad idea. That's why we have std::cbegin and std::cend in STL.
Overload resolution is concerned only with the arguments of the call (including the implicit argument for this). The expression a.get_ref() must evaluate to the same overload regardless of what happens with its returned value. That is fundamental in C++, and there's nothing you can do about it.
If you want to call the const-qualified version, use a const-qualified object expression:
const int& y = const_cast<const A&>(a).get_ref();
As I have seen from C++ Primer,fourth edition,
"What may be surprising, is that although the parameter is a const inside the function, the
compiler otherwise treats the definition of fcn as if we had defined the parameter as a plain int:"
void fcn(const int i) { /* fcn can read but not write to i */ }
void fcn(int i) { /* ... */ } // error: redefines fcn(int)
So, if I want to define two functions as follows,
int func(const int i) {
return i;
}
int func(int i){
i++;
return i;
}
Actually,they are two different functions,but when I compile it,there is a message:error:redefinition of 'int func(int i)'.Can I define them like this? Is there any alternative methods?
Parameter passed by value (like your int) are copied onto the stack,
then if your function takes a 'const int' as an argument, it does not change what can be done from outside the function (it justs forbids you from changing the value inside the function). Since the parameter is copied, a function that takes an int as an argument can be called on a const int (it will not modify the argument since it gets a copy).
Since there is no difference 'seen from outside' of the function between the one that takes an int and a const int, there would be no reason for the compiler to choose one version or the other, this is why the C++ norm states that it is the same function.
Now if you have a function that takes a reference f(int &) and another one that takes a const reference f(const int&), it is another story: then only the second one can be called with a const int argument like in:
const int x=5;
f(x);
const just modifies how you access your variable it doesn't change its data type, as far as the compiler is concerned both of them are integers and that's how they are stored whether modifiable or not.
The only workaround i see is to overload the function by changing the data type of your parameter, maybe long, double, byte etc..
Why is following not allowed in C++
#include <iostream>
class Sample {
public:
void Method(char x);
void Method(char const x);
};
void Sample::Method(char x) {
char y = x;
}
void Sample::Method(char const x) {
char y = x;
}
int main() {
Sample s;
return 0;
}
Why is following not allowed in C++?
The reason is the very same that the compiler gives you as an compilation error:
Because they are ambiguous!
Why are these methods ambiguous?
Short answer: Because the C++ Standard says so.
What is the rationale behind these overloaded methods being ambiguous?
The compiler does not know whether the caller wants to treat the value of the passed argument as an const or not, there is no way for the compiler to determine that with the information at hand.
Note the emphasis on pass by value here, the argument is being passed by value, and hence the ambiguity. If the argument was passed by reference then the compiler knows for sure how the caller wants to treat the argument because then the actual object itself is being passed, and hence compiler can make a selection of the proper overload.
The following example gives a clearer idea to the explanation above.
Online Sample:
class Sample
{
public:
void Method(char &x){}
void Method(char const x){}
void Method(char const &x){}
};
int main()
{
Sample s;
return 0;
}
It doesn't really answer why, but it is determined by the standard, §1.3.10
The information about a function that participates in overload resolution (13.3): the types of its parameters
and, if the function is a class member, the cv- qualifiers (if any) on the function itself and the class in which the member function is declared.
This just means the cv qualifiers of the arguments are ignored in the overload resolution.
A similar (but not equivalent) example with references works:
class Sample {
public:
void Method(char& x) {}
void Method(const char& x) {}
};
because here the types are different, the first case being a reference to char, the second a reference to const char (as opposed to a const reference to char).
When it comes to function parameters, char and char const are the same data type.
This is still ambiguous. When it's called with a character argument, one version will copy the argument and say "OK, you can change the copy". The other will copy the argument and say "OK, you cannot change the copy." How is the compiler supposed to know whether it can or can't change a copy of something? It could do either just fine.
because it's ambiguous
when you're passing like this
s.Method('x');
what version should you think be called?
The standard says those two declarations are equivalent (13.1.3):
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.
typedef const int cInt;
int f(int);
int f(const int); // redeclaration of f(int)
int f(int) { /* ... */ } // definiton of f(int)
int f(cInt) { /* ... */ } // error: redefiniton of f(int)
http://duramecho.com/ComputerInformation/WhyHowCppConst.html
Because const denotes that variable as having a set value, that cannot be changed after declaration. It is not a different data type.
Does declaring something like the following
void foo(int x) { std::cout << "foo(int)" << std::endl; }
void foo(const int &x) { std::cout << "foo(const int &)" << std::endl; }
ever make sense? How would the caller be able to differentiate between them? I've tried
foo(9); // Compiler complains ambiguous call.
int x = 9;
foo(x); // Also ambiguous.
const int &y = x;
foo(y); // Also ambiguous.
The intent seems to be to differenciate between invocations with temporaries (i.e. 9) and 'regular' argument passing. The first case may allow the function implementation to employ optimizations since it is clear that the arguments will be disposed afterwards (which is absolutely senseless for integer literals, but may make sense for user-defined objects).
However, the current C++ language standard does not offer a way to overload specifically for the 'l/r-valueness' of arguments - any l-value being passed as argument to a function can be implicitly converted to a reference, so the ambiguity is unavoidable.
C++11 introduces a new tool for a similar purpose — using r-value references, you can overload as follows
void foo(int x) { ... }
void foo(const int &&x) { ... }
... and foo(4) (a temporary, r-value passed as argument) would cause the compiler to pick the second overload while int i = 2; foo(i) would pick the first.
(note: even with the new toolchain, it is not possible to differentiate between the cases 2 and 3 in your sample!)
You could do this with a template:
template<typename T> void foo(T x) { ... }
Then you can call this template by value or by reference:
int x = 123;
foo<int>(x); // by value
foo<int const&>(x); // by refernce
How would the caller be able to differentiate between them?
It cannot be differentiated in this case. Both the overloaded functions have the same type of primitive data type as the argument. And taking by reference doesn't count for a different type.
You can use static_cast to explicitly select the overload to be called:
#include <iostream>
void foo(int x) { std::cout << "foo(int)" << std::endl; }
void foo(const int &x) { std::cout << "foo(const int &)" << std::endl; }
int main()
{
int x = 0;
auto f1 = static_cast< void(*)(int) >(foo);
f1(x);
auto f2 = static_cast< void(*)(const int&) >(foo);
f2(x);
}
However, you should ask yourself why you provided those two overloads in the first place. Either you are fine with making a copy or you are not. Both at the same time? Why? Also making it necessary for the caller to explicitly select the overload defeats the purpse of function overloading. If you really want that consider to supply two functions instead:
void foo_copying(int x) { std::cout << "foo(int)" << std::endl; }
void foo_non_copying(const int &x) { std::cout << "foo(const int &)" << std::endl; }
Not in C++. Functional languages such as Erlang and Haskell get closer by allowing you to specify function overloads based on parameter value, but most imperative languages including C++ require overloading based on method signature; that is, the number and type of each parameter and the type of the return value.
The const keyword in the signature defines not the type of the parameter, but its mutability within the function; a "const" parameter will generate a compiler error if modified by the function or passed by reference to any function that doesn't also use const.
The compiler can't.
Both definitions of foo can be used for all 'variants' of int.
In the first foo, a copy of the int is made. Copying an int is always possible.
In the second foo, a reference to a const int is passed. Since any int can be cast to a const int, a reference to it can be passed as well.
Since both variants are valid in all cases, the compiler can't choose.
Things become different if you e.g. use the following definition:
void foo (int &x);
Now calling it with foo(9) will take the first alternative, since you can't pass 9 as a non-const int reference.
Another example, if you replace int by a class where the copy constructor is private, then the caller can't make a copy of the value, and the first foo-variant will not be used.