10

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.

Community
  • 1
  • 1
TemplateRex
  • 69,038
  • 19
  • 164
  • 304

7 Answers7

6

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.

Community
  • 1
  • 1
Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • +1 Great answer, so I rephrased the question for const member functions (sorry about that). – TemplateRex Mar 07 '13 at 19:17
  • Ha! We brewed up the same workaround. Compliments on your impressive workaround. :) – Drew Dormann Mar 07 '13 at 19:23
  • @DrewDormann: Except that you forgot to forward the constness as a compile-time value to the implementation ;) – Daniel Frey Mar 07 '13 at 19:25
  • How can you forward that to a template parameter if one of the overloads is non-const? – TemplateRex Mar 07 '13 at 19:28
  • 1
    @rhalbersma: In the non-const overload, call `private_fun(...)`, and in the const one call `private_fun(...)`. Note that `private_fun` needs to be declared `const` to be called from inside the `const` public function. You can get away with a `const_cast` in case the bool was `true`, though. – Xeo Mar 07 '13 at 19:30
  • The implementation method must be `const` itself. Of course that means it can not modify non-`mutable` members, but in some use-cases that is good enough. Or you make it a `static` method with an additional template parameter that is deduced from `*this`. (oh, Hi Xeo :) – Daniel Frey Mar 07 '13 at 19:31
  • 1
    Which member function will be called depends on the calling expression---the type of the pointer in `p->function()`, for example, and has nothing to do with the const-ness of the actual object. – James Kanze Mar 07 '13 at 19:36
5

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.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • I don't think `do_draw` being non-`const` works, as you can't invoke it with a `Base const*`, which is what you have in `draw`. – Xeo Mar 07 '13 at 19:55
  • @Xeo, oops, I got it right in the first example, but got lazy in the second :) – Jonathan Wakely Mar 07 '13 at 19:59
  • +1 for the examples. In the first example, I understand that taking const-references to non-const object invokes const members and `draw()` could show an outdated view (@Xeo also made that point in his answer). But why wouldn't `a.draw()` re-check the data? – TemplateRex Mar 07 '13 at 20:10
  • Ah, OK, I misread: `ca.draw()` will not re-check the data if that was changed through `a.update()`. – TemplateRex Mar 07 '13 at 20:20
  • Yep, the point being that within a member function you can't tell if an object is _really_ const. But with a type that doesn't allow modification you can know for sure that it won't change – Jonathan Wakely Mar 07 '13 at 20:39
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.

Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
3

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);
  }
};
Beta
  • 96,650
  • 16
  • 149
  • 150
  • +1 silly to have overlooked this as the STL used to do this for `begin()/end()` iterators (whereas now they have gone to `cbegin()/cend()`) – TemplateRex Mar 07 '13 at 19:23
  • 2
    @rhalbersma: They haven't "gone to", they just *added* those, because it was kind of a pain to get `const_iterator`s specifically from a non-`const` object (there are [some helpers you can use](http://stackoverflow.com/a/15234120/500104), to make it better). – Xeo Mar 07 '13 at 19:32
  • 2
    Note that `t.is_const()` doesn't mean changing `t` will be well-defined or undefined, as casts mean people can change the constness at will. – GManNickG Mar 07 '13 at 19:32
  • @GManNickG Good point, but I tend to worry more about Murphy than Machiavelli (free after Herb Sutter) – TemplateRex Mar 07 '13 at 19:51
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 constness:

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.

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • +1 for the Standard quote and the example. I also need a different return type (as does `begin()` with `iterator` vs `const_iterator`), but that works as well. – TemplateRex Mar 07 '13 at 19:48
2

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.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
2

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
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331