2

I would like a class Value, which both has a run-time polymorphic behaviour, and a value semantics. For instance, I would like to be able to do things like:

// create polymorphic data
Value v1 = IntValue(42);
Value v2 = DoubleValue(12.3);

// copy-by-value semantics
Value v3 = v1; 
v3.increments();
Value v4;
v4 = v2;
v4.increments();

// possibly put them in my favourite container
MyList<Value> l;
l << v1 << v2 << v3 << v4;

// print them: "Int(42) Double(12.0) Int(43) Double(13.0) "
for(int i=0; i<l.size(); i++) l[i].print();

Is it possible, and if yes, how?

Note: Using boost or C++11 smart pointers as here is not desired: they make the caller code verbose, use -> instead of ., and do not have copy constructors or assignment operators implementing a true value semantics. Also, this question doesn't target specifically containers.

Community
  • 1
  • 1
Boris Dalstein
  • 7,015
  • 4
  • 30
  • 59
  • If you thing this is a duplicate of [this](http://stackoverflow.com/questions/8066372/smart-pointers-as-class-members-for-polymorphism), [this](http://stackoverflow.com/questions/8058625/c-object-parameters-polymorphism-value-semantics-object-lifetimes), or [this](http://stackoverflow.com/questions/41045/can-i-have-polymorphic-containers-with-value-semantics-in-c), I'm fine with this decision. But I couldn't find a clean question of this specific requirements, hence I had trouble to find the answer myself, and this post may help somebody else. – Boris Dalstein Jun 18 '13 at 02:03
  • You should have waited until "somebody else" asked the question. – Beta Jun 18 '13 at 02:15
  • I know the question is tagged c++, but that last line of sample code isn't. – Alastair Jun 18 '13 at 02:17
  • It sounds like you want a nonpolymorphic class with a polymorphic impl. – Nathan Ernst Jun 18 '13 at 02:17
  • @Beta I wasn't sure, it is written in the FAQ it is OK to this, to avoid people spending time answering if you have already answered the question by yourself. – Boris Dalstein Jun 18 '13 at 02:24
  • @Alastair indeed, this is Qt addition to C++... I thought it made the code more concise (while still obvious), and illustrating that potentially non-STD containers would be used. – Boris Dalstein Jun 18 '13 at 02:25
  • @NathanErnst Yes. Nonpolymorphic in the definition of C++, but polymorphic "in essence" :) – Boris Dalstein Jun 18 '13 at 02:26
  • @Boris Thanks, I see it now. The relevant `foreach` doco says "The keyword is a Qt-specific addition to the C++ language, and is implemented using the preprocessor." ... Which doesn't make much sense to me (if it's implemented in the preprocessor, how can it be an addition to the language?) but I guess it's all moot in a c++11 world anyway. – Alastair Jun 18 '13 at 02:39
  • @Alastair No, nothing to do with C++11. Qt uses in fact more than only the preprossessor (even though for this specific "addition" it may be the case) to create a superset of the language. Instead of compiling using your favourite C++ compiler, you first compile using "qmake", that converts the code into legal C++ and create for you a Makefile. Then you can use your favourite C++ compiler alongside this Makefile. – Boris Dalstein Jun 18 '13 at 03:05
  • @Alastair Anyway, I've removed the Qt specific code, since it appears it can be confusing, and is not related to the question. I guess I just got addicted to this syntax ;) – Boris Dalstein Jun 18 '13 at 03:15
  • Since I can't sleep tonight I decided to update solution in your self-answer. Originally I was going to post it as an answer but I'm just too lazy ;). It uses templates, `std::unique_ptr`, a base _interface_, and even a specialization for `void`. Yes, it still uses the PIMPL idiom - that I didn't change. You can find a live working copy at http://ideone.com/S9AQrU – Captain Obvlious Jun 18 '13 at 04:14
  • @Boris I just meant that relying on the preprocessor to do this type of thing is entirely redundant with c++11 native range-based for loops. – Alastair Jun 20 '13 at 20:40

3 Answers3

4

polymorphic_value has been proposed for standardisation and has some of the semantics you require. You'll have to define your own operator << though.

A polymorphic_value<T> may hold a an object of a class publicly derived from T, and copying the polymorphic_value will copy the object of the derived type.

polymorphic_value<T> is implemented with type erasure and uses the compiler-generated copy-constructor of the derived objects to correctly copy objects stored as polymorphic_value<BaseType>.

Copy constructors and assignment operators are defined so that the objects are value-like. There is no need to use or define a custom clone method.

In brief:

template <class T>
struct control_block 
{
  virtual ~control_block() = default;
  virtual T* ptr() = 0;
  virtual std::unique_ptr<control_block> clone() const = 0;
};

template <class T>
class polymorphic_value {
  std::unique_ptr<control_block<T>> cb_;
  T* ptr_ = nullptr;

 public:
  polymorphic_value() = default;

  polymorphic_value(const polymorphic_value& p) :
    cb_(p.cb_->clone())
  {
    ptr_ = cb_->ptr();
  }

  T* operator->() { return ptr_; }
  const T* operator->() const { return ptr_; }

  T& operator*() { return *ptr_; }
  const T& operator*() const { return *ptr_; }

  // Some methods omitted/deferred.
};

Specializations of the control block allow other constructors to be defined.

Motivation and design is discussed here :

https://github.com/jbcoe/polymorphic_value/blob/master/talks/2017_1_25_cxx_london.md

and here

https://github.com/jbcoe/polymorphic_value/blob/master/draft.md

A full implementation with tests can be found here:

https://github.com/jbcoe/polymorphic_value

jbcoe
  • 3,611
  • 1
  • 30
  • 45
  • That sounds really interesting, thank you for the answer! – Boris Dalstein Jan 29 '17 at 01:14
  • Please let me know if it does not solve the problem you have. – jbcoe Jan 29 '17 at 09:02
  • Unfortunately, I'm not working on that codebase anymore, my question is almost 4 years old ;-) In my case, the class with value-like semantics actually required to hold either an int or a double (it was not just a minimal example), so I'm not sure your solution would have applied. Or maybe as a polymorphic_value? I think a solution along the lines of type erasure, or QtVariant or Pixar's VtValue (https://github.com/PixarAnimationStudios/USD/blob/master/pxr/base/lib/vt/value.h) was closer to what I was looking at the moment. Nonetheless, I really like the ideas behind polymorphic_value! – Boris Dalstein Jan 30 '17 at 09:49
3

It's hard to know what you're trying to achieve here, but at first guess it seems that the (upcoming) Boost Type Erasure library might be suitable?

any<
    mpl::vector<
        copy_constructible<>,
        typeid_<>,
        incrementable<>,
        ostreamable<>
    >
> x(10);
++x;
std::cout << x << std::endl; // prints 11

(Example from docs).

Alastair
  • 4,475
  • 1
  • 26
  • 23
  • I don't know, I didn't quite get how extendible this boost library can be. Here, I use "increments" for the sake of example, but many other specific methods could be needed. – Boris Dalstein Jun 18 '13 at 02:39
  • @Boris did you check out the documentation link? The very next example after the one I posted showed how it would be used with member functions. – Alastair Jun 18 '13 at 02:43
  • Indeed, I don't know how I didn't see it, I read too fast. It looks it can satisfy the requirements (+1). Still, I find the definition of concepts via macro then the use of templates pretty cumbersome. But probably not more than my built in solution ;-) (But if it is upcoming, my solution is more appropriate for now and do not requires boost) – Boris Dalstein Jun 18 '13 at 02:55
2

Yes, it is possible, but of course there must be some hidden pointer, and the actual data must be stored on the heap. The reason is that the actual size of the data cannot be known at compile-time, and then can't be on the stack.

The idea is to store the actual implementation through a pointer of a polymorphic class ValueImpl, that provides any virtual method you need, like increments() or print(), and in addition a method clone(), so that your class Data is able to implement the value semantics:

class ValueImpl
{
public:
    virtual ~ValueImpl() {};
    virtual std::unique_ptr<ValueImpl> clone() const { return new ValueImpl(); }
    virtual void increments() {}
    virtual void print() const { std::cout << "VoidValue "; }
};

class Value
{
private:
    ValueImpl * p_; // The underlying pointer

public:
    // Default constructor, allocating a "void" value
    Value() : p_(new ValueImpl) {}

    // Construct a Value given an actual implementation:
    // This allocates memory on the heap, hidden in clone()
    // This memory is automatically deallocated by unique_ptr
    Value(const ValueImpl & derived) : p_(derived.clone()) {}

    // Destruct the data (unique_ptr automatically deallocates the memory)
    ~Value() {}

    // Copy constructor and assignment operator:
    // Implements a value semantics by allocating new memory 
    Value(const Value & other) : p_(other.p_->clone()) {}
    Value & operator=(const Value & other) 
    {
        if(&other != this)
        {
            p_ = std::move(other.p_->clone());
        }
        return *this;
    }

    // Custom "polymorphic" methods
    void increments() { p_->increments(); }
    void print()      { p_->print(); }
};

The contained pointer is stored inside a C++11 std::unique_ptr<ValueImpl> to ensure the memory is released when destroyed or assigned a new value.

The derived implementations can finally be defined the following way:

class IntValue : public ValueImpl
{
public:
    IntValue(int k) : k_(k) {}
    std::unique_ptr<IntValue> clone() const
    {
        return std::unique_ptr<IntValue>(new IntValue(k_)); 
    }
    void increments() { k_++; }
    void print() const { std::cout << "Int(" << k_ << ") "; }

private:
    int k_;
};

class DoubleValue : public ValueImpl
{
public:
    DoubleValue(double x) : x_(x) {}
    std::unique_ptr<DoubleValue> clone() const
    {
        return std::unique_ptr<DoubleValue>(new DoubleValue(k_)); 
    }
    void increments() { x_ += 1.0; }
    void print() const { std::cout << "Double(" << x_ << ") "; }

private:
    int x_;
};

Which is enough to make the code snippet in the question works without any modification. This provides run-time polymorphism with value semantics, instead of the traditional run-time polymorphism with pointer semantics provided built-in by the C++ language. In fact, the concept of polymorphism (handling generic objects that behave differently according to their true "type") is independent from the concept of pointers (being able to share memory and optimize function calls by using the address of an object), and IMHO it is more for implementation details that polymorphism is only provided via pointers in C++. The code above is a work-around to take advantage of polymorphism when using pointers is not "philosophically required", and hence ease memory management.

Note: Thanks for CaptainObvlious for the contribution and his evolved code available here that I partially integrated. Not integrated are:

  • To ease the creation of derived implementations, you may want to create an intermediate templated class
  • You may prefer to use an abstract interface instead of my non-abstract base class
Boris Dalstein
  • 7,015
  • 4
  • 30
  • 59
  • 1
    smart pointers are about lifetime management and ownership semantics. Using `std::unique_ptr` does not diminish or remove value semantics in any way. You gain far more by using it than not. – Captain Obvlious Jun 18 '13 at 03:53
  • @CaptainObvlious Thank you for you code update, it is very helpful! I had actually already implemented a templated helper almost identical than yours for my real-life code, but found it made the answer more complicated than necessary ;-) Using the unique_ptr -in addition- is indeed a nice plus to avoid manual deletion. I also particularly appreciate the way you define an implementation for void. But how does it differ from using my non-abstract interface? In my case, I could have defined the default constructor `Value() : value_(new ValueImpl()) {}`. – Boris Dalstein Jun 18 '13 at 06:27
  • @CaptainObvlious By the way, I really prefer your terminology `Value` instead of `Data`... I was trying to find a generic name for something with value semantics, this one was so obvious that I couldn't see it ;) – Boris Dalstein Jun 18 '13 at 06:32
  • absolutely no need for heap storage. you can make a pool on the static space and an allocator that gives out pointer to that space. not only that, but also the stack space, for example reserve a big char array in the main() somewhere and pass the pointer and size to an allocator, same thing. – v.oddou Apr 07 '15 at 01:21