user92382
user92382

Reputation: 389

Generic container for single type and single element in C++

I need a container/wrapper C++ class which holds a single, arbitrary value. Once this value is set only values of the same type should be accepted.

This is the code I've been experimenting with.

struct Genome {

struct FitnessConcept {};

template<typename T>
struct Fitness : public FitnessConcept{
    T value;        
    Fitness(T value) : value(value){}
};

std::shared_ptr<FitnessConcept> fitness;

template<typename T> 
void setFitness(T value) {
    fitness.reset(new Fitness<T>(value));               
}

template<typename T>
T getFitness() {
    return static_cast<Fitness<T>*>(fitness.get())->value;
}           
};

While Genome can hold arbitrary values, it does not restrict the type once the first is set, i.e. the following code is valid:

Genome g;
g.setFitness(0.2);
g.setFitness("foo"); //this should fail

UPDATE

Both compile and runtime failures are ok.

Upvotes: 4

Views: 1132

Answers (4)

TemplateRex
TemplateRex

Reputation: 70516

Apart from using libraries like Boost.Any, a small modification of your source already works. Simply add a templated constructor to initialize the Genome, and have an equality test using the (implementation defined) typeid() operator between the new and old types.

#include<stdexcept>
#include<memory>
#include<typeinfo>

class Genome 
{
private:
        class FitnessConcept 
        {
        public:
                virtual ~FitnessConcept() {}
        };

        template<typename T>
        class Fitness
        : 
                public FitnessConcept
        {
        public:
                explicit Fitness(T v)
                : 
                        value_(v) 
                {}      

                T value() 
                { 
                        return value_; 
                }

        private:
                T value_;                   
        };

public:
        template<typename T>
        explicit Genome(T v)
        : 
                fitness_(new Fitness<T>(v)) 
        {}

        template<typename T> 
        void setFitness(T v) 
        {
                auto u = std::make_shared< Fitness<T> >(v);
                if (typeid(fitness_).name() == typeid(u).name())
                        fitness_ = u;
                else
                        throw std::invalid_argument("cannot change initialized genome type\n");
        }

        template<typename T>
        T getFitness() {
                return static_cast<Fitness<T>*>(fitness_.get())->value();
        }

private:
        std::shared_ptr<FitnessConcept> fitness_;           
};

int main()
{
        Genome g(1.0);          // OK
        g.setFitness(2.0);      // OK
        g.setFitness("3.0");    // throws exception

        return 0;
}

Output on Ideone. There are several variations possible. E.g. if you do a dynamic_cast< u->get() >(fitness_->get()) then this will throw a std::bad_cast exception if the current underlying type of Genome is not convertible to the new type. This would allow you to change Genome to derived type but not to completely unrelated types.

Upvotes: 2

pmr
pmr

Reputation: 59811

This code is based on boost::any. It uses type erasure to store an arbitrary value and prohibits assigning to it. I striped out the casting machinery to make it easier on the eyes.

Maybe you can have some success in wrapping boost::any instead of bringing a full reimplementation to bear, but I'm not sure about that and you would need to take some care with the casts.

It also prohibits copy and move, because I can't be bothered to deal with that, but your full implementation should have it.

You also want to give it a name that makes more sense.

#include <typeinfo>
#include <iostream>

class reassign_any {
public:
  reassign_any() : content_(nullptr) {}
  template <typename T>
  reassign_any(const T& x) : content_(new holder<T>(x)) {}

  ~reassign_any() {
    // no need to check for null
    delete content_;
  }

  reassign_any(const reassign_any&) = delete;
  reassign_any& operator=(const reassign_any&) = delete;
  reassign_any(reassign_any&&) = delete;
  reassign_any& operator=(reassign_any&&) = delete;

  bool empty() const { return !content_; }


  template <typename T>
  bool set(const T& t) {
    if(content_) {
      // check for type equality of held value and value about to be
      // set
      if(content_->type() == typeid(T)) {
        delete content_;
        content_ = new holder<T>(t);
        return true;
      } else {
        return false;
      }
    } else {
      // just create
      content_ = new holder<T>(t);
      return true;
    }
  }

  template <typename T>
  T* get() {
    return content_->type() == typeid(T) 
      ? &static_cast<holder<T>*>(content_)->held
      : 0;
  }

private:
  class placeholder
  {
  public: // structors

    virtual ~placeholder()
    {
    }

    virtual const std::type_info & type() const = 0;
  };

  template<typename ValueType>
  class holder : public placeholder
  {
  public: // structors
    holder(const ValueType & value)
      : held(value)
    {
    }

    virtual const std::type_info & type() const
    {
      return typeid(ValueType);
    }
  public: // representation
    ValueType held;
  private: // intentionally left unimplemented
    holder & operator=(const holder &);
  };

private:
  placeholder* content_;
};

int main()
{
  reassign_any x;
  x.set(3);
  if(x.get<int>())
    std::cout << "int set" << std::endl;

  if(x.set(3.f)) {
    std::cout << "this shouldn't be printed" << std::endl;
  } else {
    std::cout << "this should be printed" << std::endl;
  }
  if(x.set(23)) {
    std::cout << "this should be printed" << std::endl;
  } else {
    std::cout << "this shouldn't be printed" << std::endl;
  }



  return 0;
}

An unrelated note: Boost.TypeErasure has recently undergone review. I would be really tempted to try and implement this version of any with it at least to see if the implementation allows for it.

Upvotes: 1

Joel
Joel

Reputation: 5674

I think the issue with what you're trying to do here is that templates, like all types, have to be fully specified at compile time. Given that restriction, there isn't much to be gained over a simple class like so:

template<typename T>
class Fitness {
    T value;        
    Fitness(T value) : value(value){}

    /* Copy construct, assign, & Compare in the usual way */

};

Upvotes: 0

Lyubomir Vasilev
Lyubomir Vasilev

Reputation: 3030

I don't know of such container type but I have a suggestion: Why not add a check in setFitness() whether the type of the value is the same as the type of the current value? That way you can guarantee that the type will be one and the same every time.

Upvotes: 0

Related Questions