Consider the following example:

#include <new>

struct FunctionObject
{
    int operator()() // non-const, modifies the state of this object
    {
        i += 1;
        j += 2;
        return i + j;
    }

    int i = 0;
    int j = 0;
};

struct Wrapper
{
    explicit Wrapper(const FunctionObject& input_object)
    {
        constructed_object = ::new (buffer) FunctionObject(input_object);
    }

    ~Wrapper()
    {
        constructed_object->~FunctionObject();
    }

    int operator()() const // const, but invokes the non-const operator() of the internal FunctionObject
    {
        return (*constructed_object)(); // this call modifies the internal bytes of this Wrapper
    }

    alignas(FunctionObject) unsigned char buffer[sizeof(FunctionObject)];
    FunctionObject* constructed_object = nullptr;
};

int test()
{
    const FunctionObject function_object{3, 4};
    const Wrapper object_wrapper{function_object}; // this call modifies the internal bytes of a const Wrapper
    return object_wrapper();
}

A Wrapper contains an internal FunctionObject which is constructed inside the Wrapper by a placement new.

The Wrapper object is const, its operator() is also const, but calling it causes the internal state of the object to be modified. In many cases, similar scenarios are undefined behavior in C++.

The question is, is it undefined behavior in this particular case (~ do I need to mark the buffer as mutable?), or does the C++ standard allow writing code like this?

3

There are 3 answers

0
1201ProgramAlarm On BEST ANSWER

This is Undefined Behavior.

From [dcl.type.cv],

Any attempt to modify a const object during its lifetime results in undefined behavior.

Adding the mutable specifier to buffer will allow it to be modified by a const member function.

0
plexando On

From [class.mfct.non-static.general/4]:

A non-static member function may be declared const [...]. These cv-qualifiers affect the type of the this pointer. They also affect the function type of the member function; a member function declared const is a const member function [...].

Then from [class.this/1]:

The type of this in a member function whose type has a cv-qualifier-seq cv and whose class is x is "pointer to cv x". [Note 1: Thus in a const member function, the object for which the function is called is accessed through a const access path. -- end note]

And eventually from [dcl.type.cv/3-4]:

[...] a const-qualified access path cannot be used to modify an object [...]

Any attempt to modify a const object during its lifetime results in undefined behavior.

Hence, your test() routine shows undefined behavior, and that even if your Wrapper object object_wrapperwasn't const.

1
DU Jiaen On

No. It won't be undefined behaviour from practice, forget about what "standard" describes. It will behave just like without the "const".

The reason is that the "const" qualifier for an object in C++ is merely to tell the compiler to generate a compilation error in the case where some members or functions inside the object get invoked. Besides that, adding a "const" qualifier to an object doesn't results in different binaries after compilation.

This is different from the "const" qualifier applied on primitive types, such as "const int", "const char *". A variable of type "const int" will probably replaced by its compile-time value for optimization purpose. A string literal of type "const char *" will refer to some memory page that has read access limitation by the OS in such a way that modifying the memory content will let to crash of the program.

Also, a "const" qualifier of a function does result in different function signatures and can be treated like a function overloading.