The following code does not compile:
#include <iostream>
class Foo {
std::string s;
public:
const std::string& GetString() const { return s; }
std::string* GetString() { return &s; }
};
int main(int argc, char** argv){
Foo foo;
const std::string& s = foo.GetString(); // error
return 0;
}
I get the following error:
const1.cc:11: error:
invalid initialization of reference of type 'const std::string&'
from expression of type 'std::string*
It does make some sense because foo
is not of type const Foo
, but just Foo
, so the compiler wants to use the non-const function. But still, why can't it recognize that I want to call the const GetString
function, by looking at the (type of) variable I assign it to? 开发者_JS百科I found this kind of surprising.
The return type is determined from the overloaded function which is actually called, it never forms a part of overload resolution itself. (What if the return type wasn't used?)
const
isn't the problem with the return value as you can bind a non-const
object to a const
reference, it's the fact that your function is returning a pointer which you don't dereference.
As foo
is not const
, the non-const
GetString()
is called - it is a better match for a non-const
object. You need:
const std::string& s = *foo.GetString();
I can't recall exactly why they don't allow overloading on return type (I think it's because return values can be discarded and thus the function wouldn't be distinct), but you can fix the problem with a const_cast hint to the compiler:
const std::string& s = const_cast<const Foo&>(foo).GetString();
To extend MarkB's answer with a little illustration of a potentially worse scenario than discarding the return value:
#include <cmath>
#include <complex>
struct Foo {
int x;
double y;
std::complex<char> z;
// etc, etc
};
int evaluate(Foo f) { return f.x; }
double evaluate(Foo f) { return f.y; }
std::complex<char>(Foo f) { return f.z; }
//etc, etc
template <typename T> class Thingamajig
{
public:
enum { value = sizeof (T); };
};
template <>
class Thingamajig<double> class Thingamajig
{
public:
int value(int a) { return a/3; }
};
template <typename T> Thingamajig<T> thingamatize(const T& t)
{
return Thingajamig<T>();
}
Foo myfoo = { 1, 2, 3 };
size_t result = sizeof(thingamatize(std::abs(4 - evaluate(myfoo))).value('B'));
It is a very lengthy process to determine that double evaluate(Foo f)
ought to be chosen.
Read also: http://blogs.msdn.com/ericlippert/archive/2006/03/31/delegates-lambdas-type-inference-and-long-playing-records.aspx
"How much work do you want to create for your compiler today?™"
The return type of a function is not used when selecting the overload. That's just how the language works.
However, implicit conversions are selected based on the context. So, technically you can make it compile by returning something that is implicitly convertible both to a reference and a pointer.
#include <iostream>
#include <string>
struct Evil
{
std::string* p;
Evil(std::string* p): p(p) {}
operator std::string*() const { return p; }
operator std::string&() const { return *p; }
};
struct ConstEvil
{
const std::string* p;
ConstEvil(const std::string* p): p(p) {}
operator const std::string*() const { return p; }
operator const std::string&() const { return *p; }
};
class Foo {
std::string s;
public:
ConstEvil GetString() const { return ConstEvil(&s); }
Evil GetString() { return Evil(&s); }
};
int main(int argc, char** argv){
Foo foo;
const std::string& s = foo.GetString(); // ok
return 0;
}
But the real answer is that functions overloaded on constness should have a similar return type. (I suppose this is where the convention "use pointers for mutable things, const references for immutable things" just breaks down.)
精彩评论