7

Working slightly with javascript, I realized it is ways faster to develop compared with C++ which slows down writing for reasons which often do not apply. It is not comfortable to always pass .begin() and .end() which happens through all my application.

I am thinking about extending std::vector (more by encapsulation than inheritance) which can mostly follow the conventions of javascript methods such as

.filter([](int i){return i>=0;})
.indexOf(txt2)
.join(delim)
.reverse()

instead of

auto it = std::copy_if (foo.begin(), foo.end(), std::back_inserter(bar), [](int i){return i>=0;} );

ptrdiff_t pos = find(Names.begin(), Names.end(), old_name_) - Names.begin();

copy(elems.begin(), elems.end(), ostream_iterator<string>(s, delim)); 

std::reverse(a.begin(), a.end());

But, I was wondering if it is a good idea, why already there is no C++ library for such common daily functionality? Is there anything wrong with such idea?

vica
  • 93
  • 7

2 Answers2

4

There's nothing inheritly wrong with this idea, unless you try to delete a vector polymorphically.

For example:

auto myvec = new MyVector<int>;
std::vector<int>* myvecbase = myvec;

delete myvecbase; // bad! UB
delete myvec; // ok, not UB

This is unusual but could still be a source of error.

However, I would still not recommend it.

To gain your added functionalities, you'd have to have an instance of your own vector, which means you either have to copy or move any other existing vectors to your type. It disallows you to use your functions with a reference to a vector.

For example consider this code:

// Code not in your control:
std::vector<int>& get_vec();

// error! std::vector doesn't have reverse!
auto reversed = get_vec().reverse();

// Works if you copy the vector to your class
auto copy_vec = MyVector<int>{get_vec()};
auto reversed_copy = copy_vec.reverse();

Also, it will work with only vector, whereas I can see the utility to have these functionalities with other container types.


My advice would be to make your proposed function free - not make them member of your child class of vector. This will make them work with any instance or references, and also overloadable with other container types. This will make your code more standard ( not using your own set of containers ) and much easier to maintain.

If you feel the need to implement many of those functional style utilities for container types, I suggest you to seek a library that implements them for you, namely ranges-v3, which is on the way to standardisation.


On the other side of the argument, there are valid use case for inheriting STL's class. For example, if you deal with generic code and want to store function object that might be empty, you can inherit from std::tuple (privately) to leverage empty base class optimization.

Also, it happened to me sometime to store a specific amount of elements of the same type, which could vary at compile time. I did extended std::array (privately) to ease the implementation.

However note something about those two cases: I used them to ease the implementation of generic code, and I inherited them privately, which don't expose the inheritance to other classes.

SebastianK
  • 3,582
  • 3
  • 30
  • 48
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • `delete a vector polymorphically` -> what does it mean? is there any example? – vica Mar 02 '18 at 05:53
  • `which means you either have to copy or move any other existing vectors to your type` -> I didn't understand this part too. – vica Mar 02 '18 at 05:54
  • By generalizing, do you recommend using templates? – vica Mar 02 '18 at 05:55
  • @vica deleting polymorphically is the action of freeing and calling the destructor of a class through a pointer to one of its base class. In other words, deleting polymorphically happen when you delete on a pointer to the base class. This is undefined behavior unless you declare the destructor as `virtual`, which the STL choose not to. – Guillaume Racicot Mar 02 '18 at 05:56
  • But if my derived class does not have a destructor at all (which I don't need too), then is everything fine with the base destructor? – vica Mar 02 '18 at 06:01
  • @vica not if you call delete through a base class pointer, remember that the compiler will generate a destructor for you if you don't happen to define one. – Guillaume Racicot Mar 02 '18 at 06:03
  • @vica see my edit. Tell me if there's still something unclear – Guillaume Racicot Mar 02 '18 at 06:08
  • Thanks a lot for your comprehensive answer. I just wonder why your `reverse` function cannot affect the original instant via calling the overload `std::reverse(a.begin(), a.end())`? – vica Mar 02 '18 at 06:16
  • @vica you can call `std::reverse` on the original instance. However, the `std::vector` class don't have the `reverse()` member function you defined. To make the member function available, you must cast the vector instance to an instance of your type having those never functions. The process of casting is done via a copy (or a move). – Guillaume Racicot Mar 02 '18 at 06:21
  • @vica this is where a non member function is preferable: there is no casting needed, since a free function can receive the vector without changing it's type. – Guillaume Racicot Mar 02 '18 at 06:22
  • I am also thinking that one might argue why not making all the proposed function free out of the class? – vica Mar 02 '18 at 06:27
  • @vica yes you got it! I was just using the reverse as an example. – Guillaume Racicot Mar 02 '18 at 06:33
2

A wrapper can be used to create a more fluent API.

template<typename container >
class wrapper{
public:
  wrapper(container const& c) : c_( c ){}

  wrapper& reverse() {
    std::reverse(c_.begin(), c_.end());
    return *this;
  }

  template<typename it>
  wrapper& copy( it& dest ) {
    std::copy(c_.begin(), c_.end(), dest ); 
    return *this;
  }

  /// ...

private:    
  container c_;
};

The wrapper can then be used to "beautify" the code

std::vector<int> ints{ 1, 2, 3, 4 };

auto w = wrapper(ints);    
auto out = std::ostream_iterator<int>(std::cout,", ");

w.reverse().copy( out );

See working version here.

Thomas
  • 4,980
  • 2
  • 15
  • 30