1

I am writing a vector class and I would like it to have the following characteristics:

  1. Use static allocation on the stack whenever possible (to avoid calling new for efficiency).
  2. Be able to be instantiated from a pointer if the user prefers to provide a previously allocated array.
  3. The class needs to be easily converted to a simple pointer. This allows to use previously written routines in C.

Find below this simple test problem with the solution I came up with. I use inheritance so Vector inherits from Vector_base which provides a common interface (pure virtual) for all vectors. Then I define an empty class Vector that allows me then using partial specialization to have different storage schemes; static or dynamic.

The idea behind this is that I just want vector to be a C++ wrapper to the old-fashioned static array.

I like the implementation below. I'd like to keep the interface I came up with in main.

What I don't like is that sizeof(Vector3) = 32 when in C a vector of three doubles is 24 bytes. The reason for this is the extra 8 bytes of the virtual table.

My question: can I somehow come up with another design that would provide me with the same interface but the vector only has 24 bytes?

Summarizing:

  1. I'd like have a Vector3 of 24 bytes, as in C.
  2. I still want to have arbitrarily large vectors though (with <double,n>)
  3. I'd like to keep the interface used in main().

Could I use a programming idiom like traits or polices for this? I am very new to those and I don't know if they could provide a solution.

Find my little test code below:

#include <iostream>
using namespace std;

#define TRACE0(a) cout << #a << endl; a;
#define TRACE1(a) cout << #a "=[" << a << "]" << endl;

enum alloc_type {Static,Dynamic};

template <class T>
class Vector_base{
public:
  Vector_base(){}
  virtual operator T*() = 0;
  virtual T operator[](int i)const = 0;
  virtual T& operator[](int i) = 0;
  virtual int size() const = 0;
  friend ostream& operator<<(ostream &os,const Vector_base& v){
    for (int i=0; i<v.size(); i++)
      cout << v[i] << endl;
    return os;
  }
};

// base template must be defined first
template <class T, int n,alloc_type flg=Static>
class Vector{};

//Specialization for static memory allocation.
template <class T, int n>
class Vector<T,n,Static>: public Vector_base<T>{
public:
  T a[n];
public:
  Vector() { 
    for (int i=0; i<n; i++) a[i] = 0; 
  }
  int size()const{return n;}
  operator T*(){return a;}
  T operator[](int i)const {return a[i];}
  T& operator[](int i){return a[i];}
};

//Specialization for dynamic memory allocation
template <class T,int n>
class Vector<T,n,Dynamic>: public Vector_base<T>{   //change for enum. flg=0 for static. flg=1 for dynamic. Static by default
public:
  T* a;
public:  
  Vector():a(NULL){
  }  
  Vector(T* data){ //uses data as its storage
    a = data;
  }
  int size()const{return n;}
  operator T*(){return a;}
  T operator[](int i)const {return a[i];}
  T& operator[](int i){return a[i];}
};

//C++11 typedefs to create more specialized three-dimensional vectors.
#if (__cplusplus>=201103L)
template <typename Scalar,alloc_type flg=Static>
using Vector3 = Vector<Scalar,3,flg>;
#else
#error A compiler with the C++2011 standard is required!
#endif

int main(){

  cout << "Testing Vector3: " << endl;

  //Vector<double,3> v3;
  Vector3<double> v3;
  TRACE0(cout << v3 << endl;);
  TRACE1(sizeof(v3));

  //Vector<double,3,Dynamic> v0(v3);
  Vector3<double,Dynamic> v0(v3); //calls Vector<double,3,Dynamic>::Vector(double*) and uses the conversion operator on v3.
  TRACE1(sizeof(v0));
  TRACE1(sizeof(double*));

  TRACE0(v3[1] = 2.1;);
  TRACE0(cout << v0 << endl;);

  return 0;
}
Casey
  • 41,449
  • 7
  • 95
  • 125
Alejandro
  • 1,064
  • 1
  • 8
  • 22
  • What's wrong with `std::vector`? – Shoe May 07 '14 at 19:17
  • @Jefffrey, This is targeted to a very efficient implementation for numerical computations. The idea is then to call very specialized C routines to perform dot/cross products, norms, matrix-vector multiply, etc. std::vector was not designed for that end. – Alejandro May 07 '14 at 19:19
  • `sizeof(Vector)` is an implementation detail that just doesn't matter. You are suffering from pre-optimization syndrome. I recommend a night of sleep and to worry about efficiency when it really is a problem. – Shoe May 07 '14 at 19:19
  • @Jefffrey, yes I do, but that is part of my job and I get paid for that :). – Alejandro May 07 '14 at 19:21
  • @Alejandro You are clearly misinformed. `std::vector` has `std::vector::data` to give you the "simple pointer" in C++11 and you can use `&vector[0]` to get it in C++03. `std::vector` *is* designed for that end. C compatibility was one of the early major design points of the C++ standard library. You can further customise `std::vector` with an allocator to use the automatic storage rather than using `new` as well. – Rapptz May 07 '14 at 19:21
  • @Rapptz: I think you missed the point. Indirection and dynamic sizing are features of `std::vector` that come at a performance cost, which you don't want to pay in numerical algorithms. – Ben Voigt May 07 '14 at 19:23
  • 2
    @BenVoigt use `std::array`? v0v – Shoe May 07 '14 at 19:24
  • @Jefffrey: Doesn't seamlessly degrade to dynamic allocation for large sizes that don't fit on the stack. – Ben Voigt May 07 '14 at 19:26
  • 3
    @BenVoigt, then use `std::vector`. Can you see a pattern? – Shoe May 07 '14 at 19:26
  • @BenVoigt: The OP's interface does not seem to offer seamless degredation. – Puppy May 07 '14 at 19:26
  • So have you tried making **custom allocators** and using `std::vector`? – Thomas Matthews May 07 '14 at 19:31
  • in the future we might get `std::dynarray` which may be used to address some of the shortcomings of `std::vector` but that is not currently available in the standard. – YoungJohn May 07 '14 at 19:45
  • 1
    @DeadMG: The OP's interface is polymorphic, so you have algorithms that work on either type of vector seamlessly. – Ben Voigt May 07 '14 at 19:51
  • @BenVoigt: Pointer-iterators already offer that functionality without a terrible virtual interface on top. – Puppy May 07 '14 at 19:55
  • @DeadMG: Sure, and that's what I would use. – Ben Voigt May 07 '14 at 19:59
  • Me too. So we agree that, in fact, there's absolutely no purpose to it whatsoever and we're not gonna do his job for him. – Puppy May 07 '14 at 20:00

5 Answers5

8

All the features you want are offered as Standard or can be plugged in to existing Standard extension points.

Use static allocation on the stack whenever possible (to avoid calling new for efficiency).

Meet std::array<T, N>. It's a C++ wrapper on a C array and presents all the same characteristics.

Be able to be instantiated from a pointer if the user prefers to provide a previously allocated array.

Meet Allocators. You can code an allocator that meets the requirement that gives back already allocated memory, then simply use std::vector. Such an allocator is under consideration for future Standards along with other allocator enhancements like polymorphic allocators.

The class needs to be easily converted to a simple pointer. This allows to use previously written routines in C.

Both std::vector and std::array offer this as a triviality.

If you want to offer this choice at runtime, consider boost::variant. Rolling your own discriminated union- not advised.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • Reminder: `std::array` is only available in C++11 and beyond. Many shops are still using older versions. – Thomas Matthews May 07 '14 at 19:32
  • @ThomasMatthews: `std::array` is absolutely trivial to write in C++03 and also available in Boost. – Xeo May 07 '14 at 19:33
  • 4
    Try reading the OP's post. He already contains a static assertion for C++11 and dependence on C++11 features. – Puppy May 07 '14 at 19:33
  • I looked into these options before. Could you please look at the interface in main? do you think you can provide an implementation in terms of the STL that would satisfy my requirements with that interface?. That's all I'm asking @jeffrey, please leave personal preferences aside. – Alejandro May 07 '14 at 19:35
  • The reminder is for other people besides the OP. :-) – Thomas Matthews May 07 '14 at 19:35
  • @Alejandro, I apologize on the community's behalf for these guys. It's unfortunate when you ask a question about template specialization, get some ideas tossed at you from users who didn't pay attention to your requirements, then get nasty comments from those users when it's pointed out that they're missing the point. – Nick Strupat May 07 '14 at 20:02
1

If I understand you correctly, something like LLVM's SmallVector seems to fit the bill. It has a template parameter declaring the maximum size you want allocated on the stack, and switches to heap memory only when it grows outside that range.

If it doesn't fit your interface directly, I'm sure looking at the implementation will be very useful towards writing something similar yourself.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
0

You are talking about two policies for locating the data: either inline as a small-array optimization, or via indirection with a pointer to a dynamically allocated buffer.

There are two ways to make that policy choice: With static type information, or dynamically. The dynamic choice requires storage to indicate whether any particular vector uses the static or dynamic policy.

For a vector of doubles, you can perhaps use an illegal value in the first element (a NaN encoding) to indicate that the dynamic policy is in effect (the pointer needs to be stored overlapping the remaining elements, use a union for this).

But in other data types, all possible bit patterns are valid. For those you will require additional storage to select the policy. You might know for a particular problem that a particular bit isn't needed for the range of values, and can be used as a flag. But there's no general solution applicable to all data types.

You probably want to look at implementations of the "small string optimization". They are making the same tradeoff for improved locality of reference when the data is small enough to store directly inside the object, and also generally trying to avoid using mode space than necessary.

One thing is for sure. In order to avoid significantly increase in space requirements, you're going to need close coupling. No specialization, no inheritance, just one monolithic class that implements both policies.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
0

Ok guys. It took me all day but this is the solution I came up with and it does exactly what I want. Please share your comments and suggestions to this solution. Of course I didn't implement all methods I want. I only implemented two fake dot products to show how specific C implementations are chosen at compile time by use of templates.

The scheme is quite more complex than what I thought it would be. The basic concepts I'm using to accomplish my design requirements are:

  1. the curiously recurring template pattern.
  2. Partial specialization
  3. Traits
  4. Compile-time selection with templates (see how I decide what dot product implementation to use).

Again, thanks and please comment!! See code below

#include <iostream>
using namespace std;
#include <type_traits>

//C++11 typedefs to create more specialized three-dimensional vectors.                                                                                                                                                                                                                                                       
#if (__cplusplus<201103L)
#error A compiler with the C++2011 standard is required!
#endif

template<class T>
struct traits{};

#define TRACE0(a) cout << #a << endl; a;
#define TRACE1(a) cout << #a "=[" << a << "]" << endl;

enum {Dynamic = -1};

template<typename T,int n>
struct mem_model{
  typedef T array_model[n];
};

//Specialization to Dynamic                                                                                                                                                                                                                                                                                                  
template<typename T>
struct mem_model<T,Dynamic>{
  typedef T* array_model;
};

template<class derived_vector>
struct Vector_base: public traits<derived_vector>{ //With traits<derived_vector> you can derive the compile time specifications for 'derived_vector'                                                                                                                                                                         
  typedef traits<derived_vector> derived;
  typedef typename traits<derived_vector>::Scalar Scalar;

public:
  inline int size()const{ //Calling derived class size in case a resize is done over a dynamic vector                                                                                                                                                                                                                        
    return static_cast<const derived_vector*>(this)->size(); //derived_vector MUST have a member method size().                                                                                                                                                                                                              
  }

  inline operator Scalar*(){return a;} //All vectors reduce to a Scalar*                                                                                                                                                                                                                                                     

  inline bool IsStatic()const{return (n==Dynamic)? false: true;}

  inline int SizeAtCompileTime()const{return n;} //-1  for dynamic vectors                                                                                                                                                                                                                                                   

protected:
  using derived::n; //compile time size. n = Dynamic if vector is requested to be so by the user.                                                                                                                                                                                                                            
  typename mem_model<Scalar,n>::array_model a;  //All vectors have a Scalar* a. Either static or dynamic.                                                                                                                                                                                                                    
};

//Default static                                                                                                                                                                                                                                                                                                             
template<typename Scalar,int n>
class Vector:public Vector_base<Vector<Scalar,n> >{ //Vector inherits main interface from Vector_base                                                                                                                                                                                                                        
public:
  //Constructors                                                                                                                                                                                                                                                                                                             
  Vector(){
    //do nothing for fast instantiation                                                                                                                                                                                                                                                                                      
  }
  Vector(const Scalar& x,const Scalar& y,const Scalar& z){
    a[0] = x; a[1] = y; a[2] = z;
  }

  //                                                                                                                                                                                                                                                                                                                         
  inline int size()const{return n;}

private:
  using Vector_base<Vector<Scalar,n> >::a;

};

//Traits specialization for Vector. Put in an inner_implementation namespace                                                                                                                                                                                                                                                 
template<typename _Scalar,int _n>
struct traits<Vector<_Scalar,_n> >{
  typedef _Scalar Scalar;
  enum{
    n = _n
  };
};

double clib_dot_product_d(const int n,double* a,double* b){
  double dot = 0.0;
  for(int i=0;i<n;i++)
    dot += a[i]*b[i];
  return dot;
}
float clib_dot_product_f(const int n,float* a,float* b){
  cout << "clib_dot_product_f" << endl;
  return 1.0;
}

template<typename Scalar>
struct dot_product_selector{};

template<>
struct dot_product_selector<double>{
  template<class derived1,class derived2>
  static double dot_product(Vector_base<derived1> &a,Vector_base<derived2> &b){
    return clib_dot_product_d(a.size(),a,b);
  }
};

template<>
struct dot_product_selector<float>{
  template<class derived1,class derived2>
  static float dot_product(Vector_base<derived1> &a,Vector_base<derived2> &b){
    return clib_dot_product_f(a.size(),a,b);
  }
};

template<class derived1,class derived2>
typename Vector_base<derived1>::Scalar dot_product(Vector_base<derived1> &a,Vector_base<derived2> &b){
  //run time assert checking the two sizes are the same!!                                                                                                                                                                                                                                                                    

  //Compile time (templates) check for the same Scalar type                                                                                                                                                                                                                                                                  
  static_assert( std::is_same<typename Vector_base<derived1>::Scalar,typename Vector_base<derived2>::Scalar>::value,"dot product requires both vectors to have the same Scalar type");
  return dot_product_selector<typename Vector_base<derived1>::Scalar>::dot_product(a,b);
}

#if 0
template <typename Scalar,alloc_type flg=Static>
using Vector3 = Vector<Scalar,3,flg>;
#endif

int main(){

  cout << "Testing Vector3: " << endl;


  Vector<double,3> as;
  Vector<double,Dynamic> ad;

  TRACE1(sizeof(as));
  TRACE1(sizeof(ad));

  TRACE1(as.SizeAtCompileTime());
  TRACE1(ad.SizeAtCompileTime());

  Vector<double,3> u(1,2,3),v(-1,1,5);
  Vector<float,3> uf,vf;
  TRACE1(dot_product(u,v));

  dot_product(uf,vf);

  //dot_product(u,vf); //this triggers a compile time assertion using static_assert                                                                                                                                                                                                                                          

  return 0;
}
Alejandro
  • 1,064
  • 1
  • 8
  • 22
-1

You could simplify the Vector template specialization to...

template <class T, std::size_t Size = -1>
class Vector {
    // The statically allocated implementation
};

template <class T>
class Vector<T, -1> {
    // The dynamically allocated implementation
};

The implementations could possibly be thin wrappers around std::vector and std::array.

EDIT: This avoid the magic constant...

template<typename T = void>
class Structure {};

template<typename T, std::size_t Size>
class Structure<T[Size]> {
    T data[Size];
    // The statically allocated implementation
};

template<typename T>
class Structure<T[]> {
    T * pData;
public:
    Structure(std::size_t size) : pData(new T[size]) {}
    ~Structure() { delete[] pData; }
    // The dynamically allocated implementation
};

Instantiated like this...

Structure<int[]> heap(3);
Structure<int[3]> stack;

EDIT: Or use policies like so...

class AllocationPolicy {
protected:
    static const std::size_t Size = 0;
};
template<std::size_t Size_>
class Static : AllocationPolicy {
protected:
    static const std::size_t Size = Size_;
};
class Dynamic : AllocationPolicy {
protected:
    static const std::size_t Size = 0;
};

template <typename T, typename TAllocationPolicy = Dynamic>
class Vector : TAllocationPolicy {
    static_assert(!std::is_same<typename std::remove_cv<TAllocationPolicy>::type, AllocationPolicy>::value && std::is_base_of<AllocationPolicy, TAllocationPolicy>::value, "TAllocationPolicy must inherit from AllocationPolicy");
    using TAllocationPolicy::Size;
public:
    T data[Size];
};

template <typename T>
class Vector<T, Dynamic> : private Dynamic {
    T * data;
public:
    Vector(std::size_t size) : data(new T[size]) {}
    ~Vector() { delete [] data; }
};
Nick Strupat
  • 4,928
  • 4
  • 44
  • 56
  • This is such a bad idea I cannot even begin to express it in words. – Shoe May 07 '14 at 19:28
  • Having a class which offer a single container interface to two different implementations, one that offers a performance benefit if one knows the size at compile time is an inexplicably bad idea? – Nick Strupat May 07 '14 at 19:34
  • Or are you wondering if someone would need a `Size` of the container to be exactly the maximum possible size of an object? Either way, you've caught my interest. – Nick Strupat May 07 '14 at 19:37
  • He is referring to your choice of -1 as a special magic value. – Puppy May 07 '14 at 19:38
  • I shouldn't have to spell out that a std::size_t of -1 is guaranteed to be at least as large as the largest possible size of an object. I don't see how that could ever cause a problem. – Nick Strupat May 07 '14 at 19:39
  • What's wrong with calling the first `std::array` and the second `std::vector`? – Shoe May 07 '14 at 19:39
  • @NickStrupat The real problem with this answer is that "Use static allocation on the stack whenever possible" is a decision to be made at *runtime*. – iavr May 07 '14 at 19:41
  • The problem is that you're not adding any value on top of the existing Standard classes- it's utterly worthless. – Puppy May 07 '14 at 19:41
  • @iavr: The OP's current code does not make that decision at run-time. But constructing from an existing pointer enables this behaviour at runtime. – Puppy May 07 '14 at 19:41
  • @iavr I don't know what version of C++ you're using, but none of the ones I know about can allocate dynamically sized arrays on the stack. – Nick Strupat May 07 '14 at 19:41
  • @DeadMG A single interface to multiple implementations removes one more thing a dev needs to think about in their day. Mental bandwidth is a finite resource. – Nick Strupat May 07 '14 at 19:43
  • @DeadMG Maybe not, but to me this is what really makes sense. Otherwise, a mere alias template to either `std::array` or `std::vector` is enough for what is suggested by Nick Strupat. – iavr May 07 '14 at 19:43
  • @NickStrupat: A pity that they have TOTALLY DIFFERENT SEMANTICS. So the interface can't really be used beyond the common interfaces std::array and std::vector already offer. – Puppy May 07 '14 at 19:45
  • @DeadMG That case is implied by OP's question. The common interface is enough. – Nick Strupat May 07 '14 at 19:48
  • @iavr, you're right, an alias is probably the best bet here. I was just illustrating a simplification of the template specialization. – Nick Strupat May 07 '14 at 19:49
  • @NickStrupat No one talked about "allocating dynamically sized arrays on the stack". I was talking about methods like [short string optimization](http://stackoverflow.com/questions/2178281/small-string-optimization-for-vector). – iavr May 07 '14 at 19:49
  • @iavr: "small", not "short". Check either my answer or the page you linked to. But yes I believe that's what the OP is wondering about. – Ben Voigt May 07 '14 at 19:50
  • @iavr, dynamically allocating vs statically allocating is half of what this question is based on. No one was talking about string sorting? – Nick Strupat May 07 '14 at 19:50
  • @NickStrupat, Nick, listen. Static and dynamic containers have very different semantics. They are not the same thing. Your solution solves nothing, introduces magic constants, make unnecessary use of template specialization and adds an unnecessary wrapper. Your `Vector` and `Vector` (with `i != -1`) are two different classes, just like `std::vector` and `std::array`. The standard library has the [`Container`](http://en.cppreference.com/w/cpp/concept/Container) concept to represents an "interface". And it's a pretty sexy idea. – Shoe May 07 '14 at 19:59
  • @Jeffrey A static and dynamic container can share EXACTLY the same semantics in OP's interface. Getting the size? Same semantics. Accessing elements with an index operator? Same semantics. Converting to a pointer to the first element? Same semantics. – Nick Strupat May 07 '14 at 20:11
  • Nick, I tried that before. Imagine however I want both specializations to have a dot product. That is why I made the abstract Vector_base class. So do product would be implemented in Vector_base and available for everybody – Alejandro May 07 '14 at 20:33