0

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?

Community
  • 1
  • 1
Simon Elliott
  • 2,087
  • 4
  • 30
  • 39

5 Answers5

1

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); }
};
AndrzejJ
  • 720
  • 5
  • 11
  • We apparently posted in a short interval of one another - didn't see your answer when I posted mine. – AndrzejJ Feb 21 '12 at 14:56
  • We were answering at the same time with the same solution. I have come across this kind of situation before in real life code. – CashCow Feb 21 '12 at 18:13
  • I'll do some more work on this and probably go with either your or CashCow's solution. Unfortunately I can't give you both the accepted answer, so I'll pick whoever my final code looks closest to. – Simon Elliott Feb 21 '12 at 22:06
1

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.

CashCow
  • 30,981
  • 5
  • 61
  • 92
  • My suggestion would be to not make the data member protected, as it breaks encapsulation, but rather to use friendship. – AndrzejJ Feb 21 '12 at 15:07
  • I prefer not to give friendships from base classes to derived classes. Normally I would use a protected get() to get to the underlying. You can use private inheritence and using to bring the Get method to public from the base class if you really insist. I am just illustrating here how it can be done in this case. – CashCow Feb 21 '12 at 18:11
  • This looks promising. I'll probably go with either your or AndrzejJ's solution. – Simon Elliott Feb 21 '12 at 22:07
  • For me this is not a typical situation to use inheritance in the first place, just a trick to get the compiler do what I need. Private inheritance would work as well, but assuming the interface may be more complicated and subject to changes it becomes more problematic, as you need to declare all const functions to be public, and then maintain it. And on top of that you'd have to make the const version of the class also privately inherit the template and again expose all the inherited functions of the public interface. Any particular reason you'd not use friendship in this case? – AndrzejJ Feb 21 '12 at 22:08
  • There are ways to "seal" a class so you can't inherit from it. (private virtual inheritence). You could do that here if you religiously object to a protected member variable. But really you are arguing on style. The protected member variable looks simpler to me and the fact I don't have a virtual destructor or any virtual functions suggests other classes should not be deriving from mine. – CashCow Feb 22 '12 at 14:46
0

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.

Alexander Poluektov
  • 7,844
  • 1
  • 28
  • 32
0

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.

Baltasarq
  • 12,014
  • 3
  • 38
  • 57
0

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
}
Useless
  • 64,155
  • 6
  • 88
  • 132
  • "Obviously you need to create the holder object from a non-const Cfoo originally, though." That's a problem. I really need to be able to instantiate the class based on a const Cfoo. I don't much want to have a const_cast in there, even though it would be reasonably safe. – Simon Elliott Feb 23 '12 at 10:09
  • It's going to be difficult to have the (compile-time) type system to enforce something you don't know until runtime. You _could_ have an abstract base class and instantiate one of two derived versions in a factory function, but I think that falls foul of your _wastes space and seems clumsy_ test. Likewise for just keeping `bool amIConst` in the holder and checking it everywhere. – Useless Feb 23 '12 at 10:15