简体   繁体   中英

Making identical C++ type aliases incompatible

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) ...

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.

One way or another, this is a case of primitive obsession . Either the int s really represent something and the vector s 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.

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;
}

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();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM