44

I use std::vector<int> for two different kinds of information. I want to be sure that I don't accidentally mix the two uses.

In short, I want something like this piece of code to fail:

#include <vector>

using A = std::vector<int>;
using B = std::vector<int>;

void fa(const A&);
void fb(const B&);

void fun()
{
    A ax;
    B bx;

    fa(bx);
    fb(ax);
}

This code compiles, even though fa expects an argument of type A. Obviously, A and B are identical.

What is the simplest way to make this code compile correctly:

fa(ax);
fb(bx);

and make this code fail:

fa(bx);
fb(ax);

Of course, I can wrap std::vector<int> within another class, but then I'll need to rewrite its interface. Alternatively, I could inherit from std::vector<int>, but this is frequently discouraged.

In short, I need two incompatible versions of std::vector<int>.

EDIT

It has been suggested that Strong typedefs can solve this problem. This is only partially true. If I use BOOST_STRONG_TYPEDEF(std::vector<int>, A), I need to add some annoying casts. For example, instead of

A ax{1,3,5};

I need to use

A ax{std::vector<int>{1,3,5}};

And instead of

for (auto x : ax) ...

I need to use

for (auto x : (std::vector<int>)ax) ...
Community
  • 1
  • 1
oz1cz
  • 5,504
  • 6
  • 38
  • 58
  • Instead of wrapping the entire `std::vector` in another class, what about wrapping `int` into something more meaningful? – Bart van Nierop Dec 23 '16 at 11:14
  • @BartvanNierop I believe this would have the same effect as `using` is to type aliasing – W.F. Dec 23 '16 at 11:16
  • @W.F. Surely `class column { int id; /*...*/ };` and `class row { int id; /*...*/ };` would not be interchangeable. I wasn't suggesting `typedef`. – Bart van Nierop Dec 23 '16 at 11:18
  • @BartvanNierop yes I believe this would be a good approach. I suggest to make it an answer – W.F. Dec 23 '16 at 11:21
  • 9
    Possible duplicate of [Strong typedefs](http://stackoverflow.com/questions/28916627/strong-typedefs) – Karl Nicoll Dec 23 '16 at 11:21
  • 2
    Can you give us some context? Usually I would answer such things specific to what A and B are supposed to be. That said, a regular usage of std::vector will create vectors that should not be inserted in some other parts of the function, this is pretty obvious, right? Like a vector that defines an order which should not be inserted in a sorting algorithm. So, I don't really see why you need them to be incompatible. Of course, you will have some reasons for that, and I'd really like to hear them. They might be the key to your question. – Aziuth Dec 23 '16 at 11:33
  • 1
    @Aziuth, the vectors are used to hold two different representations of a position in a game. One representation is useful for some calculations, another representations is useful for other calculations. I want to ensure that a function that expects representation A isn't called with representation B. – oz1cz Dec 23 '16 at 12:08
  • it might be a bit cumbersome but [boost strong typedef](http://www.boost.org/doc/libs/master/libs/serialization/doc/strong_typedef.html) might be of use. [example here](http://coliru.stacked-crooked.com/a/5f789cd809ea3afe) – sp2danny Dec 23 '16 at 12:17
  • 3
    _"Alternatively, I could inherit from std::vector, but this is frequently discouraged."_ Pfft, nothing wrong with it here. You're not planning to dynamically allocate your type, or to add data members to it, are you? No. So just publicly inherit from `std::vector` to create your own types and be done with it. – Lightness Races in Orbit Dec 23 '16 at 12:23
  • @sp2danny: Regarding boost strong typedef, see my edit above. – oz1cz Dec 24 '16 at 11:20

4 Answers4

65

I think what you want is still best achieved with:

struct A : public std::vector<int>{
  using vector::vector;
};
struct B : public std::vector<int>{
  using vector::vector;
};

It does exactly what you want. There's no reason to come up with some ugly hackery just to avoid a clean statement. The main reason I see that such subtyping is not favored is that the same things should behave like they are the same and can be used interchangeably. But that is exactly what you want to suppress, and therefore subtyping it makes exactly the statement that you want: they have the same interface but they shouldn't be used the same because they aren't the same.

Davislor
  • 14,674
  • 2
  • 34
  • 49
Christoph Diegelmann
  • 2,004
  • 15
  • 26
  • He wrote in his text that he does not want to inherit. In your code, we have an example of when not to use inheritance - A and B have no difference to vector. Derived things should always be notable different. – Aziuth Dec 23 '16 at 11:29
  • 2
    @Aziuth If the only difference this has to a `using` statement is exactly the thing you want to change then there's no reason to not use it. – Christoph Diegelmann Dec 23 '16 at 11:32
  • 3
    @Aziuth: no. Type `X` is a descendant of type `Y` iff `X` _is_a_ `Y`. Two types are equal iff any of them _is_an_ other. OP clearly stated that `A` and `B` are distinct: `A` _is_not_a_ `B` (even if you can specify their state with the same parameters, their schematics differ). – lorro Dec 23 '16 at 11:43
  • @lorro I'm not talking about how the mechanics work, I'm talking about if one should implement it like that. I'm also not talking about comparing A and B, I'm talking about comparing A and vector. Again: there is no notable difference **to the developer** between those classes. That is like "class X, a child of Y, offers those functionalities: ... which clearly distinct it from it's parent class". – Aziuth Dec 23 '16 at 12:00
  • 6
    @Aziuth: vector is not polymorphic, so whatever derived class cannot act as a vector substitute, since vector method will not be redirected to the inherited class. So any OOP-related mantra (derive = is_a, don't derive if it's not virtual etc. etc.), must not apply here, because we are not in a pure OOP context (we are in a generic programming arena, here). The fact both A and B decay into vector only means that functions taking a vector can work with A and B also (but is probably what he needs). But functions requiring A will not work with either B or vector. – Emilio Garavaglia Dec 23 '16 at 12:17
  • @EmilioGaravaglia Am I that hard to understand? I am **not talking** about how the compiler will react. I am talking about design here. Yes, I totally understand that a function requiring A will not work with B, that is obvious, you don't have to tell me that. The question is if this is how this should be solved. I say no because there is nothing added in the child. It will work, yes, but it working does not mean that it is good. – Aziuth Dec 23 '16 at 12:22
  • 17
    @Aziuth: We understand what you're saying. We're saying that you're wrong. `A` does add functionality to vector: the functionality of being an `A`. That's it. Problem solved. No more work to do. – Lightness Races in Orbit Dec 23 '16 at 12:24
  • 3
    @Aziuth: Of course you can wrap the vector, instead of inherit, then rewrite all of its methods by simple delegating the vector ones. Youl' type a lot to add nothing and will be payed the same. If vector changes, you also have to change your class, and don't satisfy the DRY principle. So it's not good. You are conditioned by the "don't derive from containers / don't derive what have not virtual destructor" OOP mantra, that's completely out of context here. Respecting it in this context provides no value. It will be a good thing multiplied by zero. – Emilio Garavaglia Dec 23 '16 at 12:33
  • @EmilioGaravaglia don't care much about the don't derive from containers mantra, I'm just used to think of children being something actually new. In this case I wonder if one can simply go by with not making A and B different. I mean, if I have some integer parameter in a function and I insert an integer that is totally not created to be this parameter, this will obviously produce nonsense, but we are not thinking about making some special exclusive parameter type for that, right? Do you have some articles for that pattern used in the answer? – Aziuth Dec 23 '16 at 12:55
  • 3
    @Aziuth If C++ allowed to inherit form int, I wold have been suggested. But inheriting adding nothing, just to differentiate types is what even the standard library do when defining tag based dispatch. (see how iterator tags are declared and how they are filtered by SFINAE). For example: http://www.boost.org/community/generic_programming.html#tag_dispatching – Emilio Garavaglia Dec 23 '16 at 13:04
  • 13
    @Aziuth IMHO that's exactly the sort of thing we should be doing in programming. Making best use of the type checker is a really good thing and lets us spot many errors that would otherwise go unnoticed. – Muzer Dec 23 '16 at 13:29
  • 11
    @Aziuth It's actually very common to do just that in languages with stronger type systems (OCaml comes to mind). There is no reason that I can pass an integer specifying a temperature to a function that expects an integer signifying time in minutes - it's just an unimportant implementation detail that they're both represented by the same primitive data type, but their meaning is different and they shouldn't be interchangeable. – Voo Dec 23 '16 at 14:21
  • 1
    I didn't think you were allowed to inherit from STL containers because they don't have virtual destructors. – Trevor Hickey Dec 24 '16 at 18:35
  • @TrevorHickey Good point. I think it shouldn't be a problem in this case as it doesn't extend the base class and imports the destructor but maybe someone could ask that question or link a suitable answer if it's already on stackoverflow. – Christoph Diegelmann Dec 24 '16 at 20:34
  • 1
    I'm surprised by the `using` syntax being used here. Shouldn't that be `using std::vector::vector` instead? – Marc van Leeuwen Dec 26 '16 at 07:20
  • 1
    @TrevorHickey: The language allow it. It works. I do. I do because i don't want to know about OOP silly dogmas out of a object-riented context: I just know C++. Destructor are not virtual? I Know, and I know I must not call delete fro ma pointer to a container base. I know, I'll not do. Instead of serving somebody else god's dogmas, I'll just learn the language. – Emilio Garavaglia Dec 27 '16 at 13:56
17

One way or another, this is a case of primitive obsession. Either the ints really represent something and the vectors are a collection of that something, or the vector<int>s represent something.

In both cases, this should be solved by wrapping the primitive up into something more meaningful. For example:

class column
{
  int id;
  /*...*/
}; 
class row
{
  int id;
  /*...*/
};

std::vector<row> and std::vector<column> would not be interchangeable.

Of course, the same idea could be applied to vector<int> instead of int, if vector<int> is the primitive that really means something else.

Bart van Nierop
  • 4,130
  • 2
  • 28
  • 32
  • 2
    Subtyping the basic integral type with a lot of code to avoid subtyping the vector itself just moves the problem. If that is acceptable why wouldn't it be acceptable to subtype the vector? – Christoph Diegelmann Dec 23 '16 at 11:50
  • 2
    @Christoph Because it's the integers that, likely, represent different things. They just happen to be stored in a vector. That kind of [primitive obsession](https://sourcemaking.com/refactoring/smells/primitive-obsession) should be fixed. – Bart van Nierop Dec 23 '16 at 11:58
  • 3
    I like the first part of this, but not the second part. Just use a template: `template class tagged_integer { /* ... */ }; using row=tagged_integer;` – T.C. Dec 23 '16 at 12:06
  • I like that idea. – Bart van Nierop Dec 23 '16 at 12:10
  • Come to think of it, I don't really like the second part of the answer at all. It was a non-solution. I have no idea what I was thinking. – Bart van Nierop Dec 23 '16 at 12:16
  • 1
    What you call 'primitive obsession' was actually a very good solution we came up with for a design problem where multiple libraries - released separately and being unrelated - had to talk to each other. Specifying the interface in terms of `std::function<>` and base types that 'just fit' gave us null-mediators for interconnect instead of O(n^2) mediators or base-of-base libraries to depend on. Also, it allowed client to mix-and-match versions of the libs. – lorro Dec 23 '16 at 12:48
  • 2
    @lorro: API boundaries are a special case... but they are JUST boundaries. Anything that cuts a boundary should be (1) validated and (2) transformed into a domain appropriate type... ideally, in one step as validation is about establishing invariants. – Matthieu M. Dec 23 '16 at 18:56
  • What are performance implications of doing this wrapping of primitive types? – shitpoet Jan 10 '23 at 10:26
4

Alternatively, I could inherit from std::vector, but this is frequently discouraged.

IMO, it depends on the situation. In general could be a good solution

#include <vector>

class VectorA :
    public std::vector<int> {
 public:
  VectorA() = default;
  ~VectorA() = default;
  VectorA(const VectorA&) = default;
  VectorA(VectorA&&) = default;
  VectorA& operator=(const VectorA&) = default;
  VectorA& operator=(VectorA&&) = default;
};

class VectorB :
    public std::vector<int> {
 public:
  VectorB() = default;
  ~VectorB() = default;
  VectorB(const VectorB&) = default;
  VectorB(VectorB&&) = default;
  VectorB& operator=(const VectorB&) = default;
  VectorB& operator=(VectorB&&) = default;
};

You can still use VectorA and VectorB as normal vector, but you cannot switch among them.

void acceptA(const VectorA& v) {
  // do something
}

void acceptB(const VectorB& v) {
  // do something
}

template<typename T>
void acceptVector(const std::vector<T>& v) {
  // do something
}

int main(int argc, char *argv[]) {
  VectorA va;
  VectorB vb;

  acceptA(va);  // you can only pass VectorA
  acceptB(vb);  // same here for VectorB

  acceptVector(va);  // any vector
  acceptVector(vb);

  return 0;
}
BiagioF
  • 9,368
  • 2
  • 26
  • 50
4

This is partly why you can do object oriented programming in C++ as well as object based programming reusing the library types.

Make A and B classes which model the behaviour in your domain. It doesn't matter if it happens that both behaviours are implemented with fields which are vectors of ints; as long as you do no not break encapsulation, all the operations on the distinct vectors will be in the scope of their class and no confusion can occur.

#include <vector>

class A {
    std::vector<int> cake_orders_;

public:
    void f() ; // can only do something to do with cake
};

class B {
    std::vector<int> meal_worm_lengths_;

public:
    void f() ; // can only do something to do with worms
};


void fun()
{
    A ax;
    B bx;

    a.f(); // has to be the right thing
    b.f();
}
Pete Kirkham
  • 48,893
  • 5
  • 92
  • 171