Simon Elliott
Simon Elliott

Reputation: 2117

Single class which combines const and nonconst reference data member

Here's a set of C++ classes which implement a kind of adapter pattern:

#include <iostream>

class Cfoo
{
public:
    explicit Cfoo(int i):i_(i){}
    void SetI(int i){ i_ = i; }
    int GetI()const{ return(i_); }
private:
    int i_;
};

class CfooHolderConst
{
public:
    explicit  CfooHolderConst(const Cfoo& foo):foo_(foo){}
    int GetI()const{ return( foo_.GetI() ); }
private:
    const Cfoo& foo_;
};

class CfooHolderNonConst
{
public:
    explicit CfooHolderNonConst(Cfoo& foo):foo_(foo){};
    int GetI()const{ return( foo_.GetI() ); }
    void SetI(int i){ foo_.SetI(i); }
private:
    Cfoo& foo_;
};

int main(  int argc, char* argv[] )
{
    const Cfoo myConstFoo(42);
    CfooHolderConst myConstFooHolder(myConstFoo);
    std::cout << myConstFooHolder.GetI() << std::endl;

    Cfoo myNonConstFoo(1);
    CfooHolderNonConst myNonConstFooHolder(myNonConstFoo);
    myNonConstFooHolder.SetI(42);
    std::cout << myConstFooHolder.GetI() << std::endl;

    return(0);
}

I want to combine CfooHolderNonConst and CFooHolderConst into a single class, or failing that, inherit one from the other. The reference to Cfoo is a problem here, because in CFooHolderConst it needs to be defined as const Cfoo&, while in CfooHolderNonConst it needs to be Cfoo&.

This is a similar problem to the interator/const_iterator here: How to avoid code duplication implementing const and non-const iterators?

...but I'm hoping that because this doesn't have to meet the STL iterator requirements, there might be a simpler solution.

In the past I've solved this kind of problem by having both a const and nonconst pointer as class members, and setting one or the other up from overloaded constructors. This wastes space and seems clumsy. Is there a more elegant solution?

Upvotes: 0

Views: 293

Answers (5)

Useless
Useless

Reputation: 67723

Is there a specific reason why you can't just use FooHolder for non-const (mutable) access, and const FooHolder for const access?

You can't call a non-const-qualified method (like SetI) on a const object, so it seems like it does what you want. Obviously you need to create the holder object from a non-const Cfoo originally, though.

example:

class Cfoo
{
public:
    explicit Cfoo(int i) : i_(i) {}
    void SetI(int i) { i_ = i; }
    int GetI() const { return(i_); }
private:
    int i_;
};

class CfooHolder
{
public:
    explicit CfooHolder(Cfoo& foo) : foo_(foo) {};
    void SetI(int i) { foo_.SetI(i); }
    int GetI() const { return( foo_.GetI() ); }
private:
    Cfoo& foo_;
};

void bar(CfooHolder &holder, int i)
{
    holder.SetI(i); // fine
}

void bar(CfooHolder const &constholder, int i)
{
    holder.SetI(i);
    // error: method exists, but I can't call it here
}

Upvotes: 0

Baltasarq
Baltasarq

Reputation: 12212

If you really want to have the same class (providing the semantics still make sense), then I think you want something like:

const Cfoo f1( 5 );
const CfooHolder h1( f1 );

Cfoo f2( 0 );
CfooHolder h2( f2 );

I think that your hope would be for C++ to make the following decissions: a) Treat the Cfoo object as const if it was const, or non-const if it was non-const. The clue is both the definition of Cfoo and the definition of CfooHolder. If Cfoo is const, then CfooHolder must be declared const, or it should fail to compile. If Cfoo is non-const, then you can create CfooHolder's which can be both const and non-const. b) The method SetI() should cease to compile when used in a const CfooHolder object. In the example above h1.SetI( 6 ); should not compile.

My answer is that, if a) worked, then b) would automatically work as well. The problem is achieving a), which is not possible as far as I know.

For this to work, the attribute should be made const or non-const under the circunstance of an object of its class being const or non-const. Though an object of the class can change this "state", the attributes remain the same, however. But you can only use const methods when the object of this class is const (for example, when a parameter is passed by constant reference). So, C++ won't support that, because it does not work that way.

The other possibility would be to let the attribute itself be const and non-const at the same time, which does not make sense.

The short answer is: it can't be done and there will be code repetition. If you really want to avoid this, and the wrapper is enough complex to be worried, the only way is to create a general holder, and then the constant and non-constant wrappers around the general holder, avoiding repetition to the bare minimal.

class CfooHolder
{
public:
    explicit CfooHolder(Cfoo& foo):foo_(foo){};
    int GetI()const{ return( foo_.GetI() ); }
    virtual void SetI(int i){ foo_.SetI(i); }
protected:
    Cfoo& foo_;
};

class CfooHolderNonConst : public CfooHolder {
public:
    explicit CfooHolderNonConst(Cfoo& foo):CfooHolder(foo){};
};

class CfooHolderConst: public CfooHolder
{
public:
    explicit  CfooHolderConst(const Cfoo& foo):CfooHolder(const_cast<Cfoo &>( foo )){}
    void SetI(int i){ throw std::runtime_error( "Don't write to me!" ); }
};

It is not perfect, but it works under the stated terms. The method SetI() would throw a runtime error, but if the CfooHolderConst object is declared as const, then a call to SetI() won't even compile.

Hope this helps.

Upvotes: 0

CashCow
CashCow

Reputation: 31435

Yes, it can be done:

template< typename T > CHolderReader
{
 public: 
    explicit  CHolderBase( T& t):t_(t){}
    int Get()const { return t_.GetI(); }

 protected:
    ~CHolderReader() {}

 protected:
    T& t_;
};

template< typename T > CHolderReaderWriter : public CHolderReader< T >
{
public:
   void Set( int i)
   {
       t_.SetI(i);
   }
};

typedef CHolderReader<const Cfoo> CFooHolderConst;
typedef CHolderReaderWriter<Cfoo> CFooHolderNonConst;    

Actually this is intended to be an example where you wrap the getting of the underlying data in its const or non-const state. The Reader holds a non-const reference unless the templated type is const, but doesn't let you write to it, so you can extend it as with CHolderReaderWriter when you do need to write to it.

Upvotes: 1

AndrzejJ
AndrzejJ

Reputation: 740

You can create a template class that will give you const functionality for both const and non-const versions of the class, and then inheritance to extend the non-const version with functionality to modify the member:

class Cfoo
{
public:
    explicit Cfoo(int i):i_(i){}
    void SetI(int i){ i_ = i; }
    int GetI()const{ return(i_); }
private:
    int i_;
};

class CfooHolderNonConst;

template<class Foo>
class CFooHolder
{
    friend class CfooHolderNonConst;
public:
    explicit  CFooHolder(Foo& foo):foo_(foo){}
    int GetI()const{ return( foo_.GetI() ); }
private:
    Foo& foo_;
};

typedef CFooHolder<const Cfoo> CfooHolderConst;

class CfooHolderNonConst: public CFooHolder<Cfoo>
{
public:
    explicit CfooHolderNonConst(Cfoo& foo):CFooHolder(foo){};
    void SetI(int i){ foo_.SetI(i); }
};

Upvotes: 1

Alexander Poluektov
Alexander Poluektov

Reputation: 8053

I think it is good idea to have both const- and non-const- interface as a separate classes.

They provide different interfaces to their users and have different semantics. The duplication is also minimal in your example.

Upvotes: 0

Related Questions