Is it possible in C++ to achieve something like a constructor that is only allowed to create const
objects?
I am thinking of making a decorator class to an interface with const
and non const
methods. Initializing the decorator from a const
base object should only be able to produce const decorators, but initializing from a non-const should yield a fully functional decorator.
struct A
{
virtual void foo(); // requires non-const A
virtual void bar() const; // const method
};
class decorator : public A
{
private:
std::shared_ptr<A> p_impl;
public:
virtual void foo() { p_impl->foo(); }
virtual void bar() const { p_impl->bar(); }
// regular constructor
decorator(std::shared_ptr<A> const & p) : p_impl(p) {}
// hypothetical constructor that makes a const
decorator(std::shared_ptr<A const> const & p) const : p_impl(p) {}
};
void F(std::shared_ptr<A> const & regular_a
, std::shared_ptr<A const> const & const_a )
{
decorator regular_decorator(regular_a);
regular_decorator.foo(); // all good
regular_decorator.bar(); // all good
decorator bad_decorator(const_a); // compiler error
// trying to use a const constructor to init a non-const object
const decorator const_decorator(const_a); // all good
const_decorator.foo(); // compiler error, foo is not const
const_decorator.bar(); // all good
// I have a lot of these in code that is beyond my control
decorator bad_practice(const_cast<decorator&>(const_decorator));
bad_practice.foo(); // all good
}
How can I achieve a similar effect?
I only was able to get this working by having not a constructor that returns const
object, but a static function (a-la named constructor) that returns shared_ptr<const decorator>
. This 'encodes' constness in type and prohibits non-const calls:
struct A
{
virtual void foo(); // requires non-const A
virtual void bar() const; // const method
};
class decorator : public A
{
private:
std::shared_ptr<A> p_impl;
public:
virtual void foo() { p_impl->foo(); }
virtual void bar() const { p_impl->bar(); }
// regular constructor
decorator(std::shared_ptr<A> const & p) : p_impl(p) {}
static std::shared_ptr<decorator const> constDecorator(std::shared_ptr<A const> const & p) { return std::make_shared<decorator>(std::const_pointer_cast<A>(p)); }
};
void F(std::shared_ptr<A> const & regular_a
, std::shared_ptr<A const> const & const_a )
{
decorator regular_decorator(regular_a);
regular_decorator.foo(); // all good
regular_decorator.bar(); // all good
decorator bad_decorator(const_a); // compiler error
// trying to use a const constructor to init a non-const object
std::shared_ptr<const decorator> const_decorator = decorator::constDecorator(const_a); // all good
const_decorator->foo(); // compiler error, foo is not const
const_decorator->bar(); // all good
// I have a lot of these in code that is beyond my control
decorator bad_practice(const_cast<decorator&>(*const_decorator));
bad_practice.foo(); // all good
}
You can of course use shared_ptr
for non-const decorators too by declaring another static function and thus get similar usage patterns for both const and non-const.
Note that this will require you to delete copy constructor and operator=
for decorator
, as they will lose constness. However, a similar problem exists in your version with a hypothetical const constructor.
Another approach that I have tried was to make decorator
a template class and have two different types: decorator<A>
and decorator<const A>
, hoping that compiler will not instantiate decorator<const A>::foo()
unless it is used, but it keeps instantiating it even if it is not used.