可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
With decltype
and std::is_const
the constness of a variable can be externally detected. But is it also possible for an object to know its own constness? Usage should be like:
#include <type_traits>
#include <iostream>
#include <ios>
struct Test
{
Test() {}
bool print() const
{
// does not work as is explained in https://stackoverflow.com/q/9890218/819272
return std::is_const<decltype(*this)>::value; // <--- what will work??
}
};
int main()
{
Test t;
const Test s;
// external constness test
std::cout << std::boolalpha << std::is_const<decltype(t)>::value << "\n";
std::cout << std::boolalpha << std::is_const<decltype(s)>::value << "\n";
// internal constness test
std::cout << std::boolalpha << t.print() << "\n";
std::cout << std::boolalpha << s.print() << "\n"; // <--- false??
}
Output on LiveWorkSpace Is this somehow possible?
MOTIVATION: I want to be able to detect whether a const member function is invoked on a const object or is coming from a non-const object. The object could e.g. represent a cache and the member a view. If the cache was const, one could presumably use an optimized draw routine, whereas if the underlying data was non-const, the draw routine would need to do periodically check if the data was refreshed.
NOTE: the related question asks how to break the build for const objects, but I don't quite understand if the answer to that implies a definite NO for my question. If not, I want to capture the constness in a boolean for further usage.
EDIT: as was pointed out @DanielFrey, the constructor is not a good place to test for constness. What about a const member function?
UPDATE: Thanks everyone for correcting my initially ill-posed question and providing the various parts of the answer (constructors' ill-defined constness, rvaluedness of this
, the contextual meaning of const
, the -with hindsight- obvious overload trick that I had overlooked, and the const reference aliasing loophole lurking in the shadows). For me this question was Stackoverflow at its best. I decided to select @JonathanWakely's answer because it showed how to define Mutable
and Immutable
classes that strengthen the constness concept to achieve what I want in a foolproof way.
回答1:
As others have stated, you cannot tell if an object was declared as const
from within a member function. You can only tell if it's being called in a const
context, which is not the same.
MOTIVATION: I want to be able to detect whether a const member function is invoked on a const object or is coming from a non-const object. The object could e.g. represent a cache and the member a view. If the cache was const, one could presumably use an optimized draw routine, whereas if the underlying data was non-const, the draw routine would need to do periodically check if the data was refreshed.
You can't tell that reliably.
struct A
{
void draw() { fut = std::async(&A::do_draw, this, false); }
void draw() const { fut = std::async(&A::do_draw, this, true); }
void update(Data&);
private:
void do_draw(bool this_is_const) const;
mutable std::future<void> fut;
};
A a;
const A& ca = a;
ca.draw(); // object will think it's const, but isn't
Data new_data = ...;
a.update(new_data); // do_draw() should recheck data, but won't
You can model it in the type system, by defining separate mutable and immutable types.
struct Base
{
virtual ~Base();
virtual void draw() const = 0;
protected:
void do_draw(bool) const;
};
struct MutableCache : Base
{
virtual void draw() const { fut = std::async(&Base::do_draw, this, false); }
void update();
};
struct ImmutableCache : Base
{
virtual void draw() const { fut = std::async(&Base::do_draw, this, true); }
// no update function defined, data cannot change!
};
Now if a cache is create as an ImmutableCache
you know it can't change, so that replaces your previous idea of a "const" object. A MutableCache
can change, so needs to check for refreshed data.
回答2:
It's not possible for constructors (the original question) because of
12.1 Constructors [class.ctor]
4 A constructor shall not be virtual
(10.3) or static
(9.4). A constructor can be invoked for a const
, volatile
or const volatile
object. A constructor shall not be declared const
, volatile
, or const volatile
(9.3.2). const
and volatile
semantics (7.1.6.1) are not applied on an object under construction. They come into effect when the constructor for the most derived object (1.8) ends. A constructor shall not be declared with a ref-qualifier.
For member functions (the current question), you could simply provide both a const
and a non-const
overload, forward both to a (private) method that takes the constness as a boolean template parameter.
回答3:
The type of this
(and consequently of *this
) is purely determined by the cv-qualifier of the function and does not change depending on whether the actual object is cv-qualified or not.
§9.3.2 [class.this] p1
In the body of a non-static (9.3) member function, the keyword this
is a prvalue expression whose value is the address of the object for which the function is called. The type of this
in a member function of a class X
is X*
. If the member function is declared const
, the type of this
is const X*
, if the member function is declared volatile
, the type of this
is volatile X*
, and if the member function is declared const volatile
, the type of this
is const volatile X*
.
So, you can't see inside of a member function whether the object it's invoked on is const
, but you can make the compiler call different functions depending on const
ness:
struct X{
void f(){ /* non-const object */ }
void f() const{ /* const object */ }
};
int main(){
X x1;
X const x2;
x1.f(); // calls non-const 'f'
x2.f(); // calls const 'f'
// but beware:
X const& rx = x1;
rx.f(); // calls const 'f', because the "object" it's invoked on is const
}
Beware of the limitation laid out in the snippet, though.
回答4:
what will work??
Nothing will work.
The object running its constructor is never const
. It may only be assigned to a const
variable after the constructor has run its course.
It also can't be determined for member functions (non-const member functions included, since const_cast
may have been used)
Const-ness is a property that exists at each call site, not as a property of the function body itself.
MOTIVATION: I want to be able to detect whether a const member function is invoked on a const object
You can't do that, but you can come close...
class C
{
public:
void func() const
{
std::cout << "const!";
func_impl();
}
void func()
{
std::cout << "non-const!";
func_impl();
}
private:
void func_impl() const;
};
The object could e.g. represent a cache and the member a view. If the cache was const, one could presumably use an optimized draw routine, whereas if the underlying data was non-const, the draw routine would need to do periodically check if the data was refreshed.
That would be an unreliable usage of const
because const
is not a property of the object itself. It's a property of the current context that the object's being used.
Detecting that it's const
in the current context doesn't tell you that the object has always been treated in a const
context.
回答5:
I don't know if it's possible in that way, by means of a member value that's set in the constructor, but the object can report its const-ness with member functions:
struct Test
{
bool is_const() const
{
return(true);
}
bool is_const()
{
return(false);
}
};
回答6:
This isn't possible because it's possible for a particular value to be simultaneously viewed as const
and not const
. Consider
MyType t = ...;
MyType& nonConstRef = t;
const MyType& constRef = t;
At this point t
has both a const
and a non-const reference.
回答7:
Detecting constness within an object isn't possible, but you state that your motivation is…
“ I want to be able to detect whether a const member function is invoked on a const object or is coming from a non-const object.”
Well that’s easy, just provide a non-const
overload.
The overloads can defer to a common implementation, e.g. as follows:
#include <type_traits>
#include <iostream>
using namespace std;
class Test
{
private:
template< class CvTest >
static void print( CvTest& o )
{
cout << boolalpha;
cout << "> object says: Hey, const = " << std::is_const<CvTest>::value << "!" << endl;
}
public:
bool print() { return (print( *this ), false); }
bool print() const { return (print( *this ), true); }
};
int main()
{
Test t;
const Test s;
cout << "External constness test:" << endl;
cout << boolalpha << is_const<decltype(t)>::value << "\n";
cout << boolalpha << is_const<decltype(s)>::value << "\n";
cout << endl;
cout << "Internal constness test:" << endl;
cout << boolalpha << t.print() << "\n";
cout << boolalpha << s.print() << "\n";
}
Results:
External constness test:
false
true
Internal constness test:
> object says: Hey, const = false!
false
> object says: Hey, const = true!
true