使相同的C++类型别名不兼容

oz1*_*1cz 37 c++ c++11

我使用std::vector<int>两种不同的信息.我想确保我不会意外地混合这两种用途.

简而言之,我想要像这段代码一样失败:

#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);
}
Run Code Online (Sandbox Code Playgroud)

此代码编译,即使fa期望类型的参数A.显然,A并且B是相同的.

使这段代码正确编译的最简单方法是什么:

fa(ax);
fb(bx);
Run Code Online (Sandbox Code Playgroud)

并使此代码失败:

fa(bx);
fb(ax);
Run Code Online (Sandbox Code Playgroud)

当然,我可以std::vector<int>在另一个类中包装,但是我需要重写它的接口.或者,我可以继承std::vector<int>,但这通常是气馁的.

简而言之,我需要两个不兼容的版本std::vector<int>.

编辑

有人建议Strong typedef可以解决这个问题.这只是部分正确.如果我使用BOOST_STRONG_TYPEDEF(std::vector<int>, A),我需要添加一些恼人的演员阵容.例如,而不是

A ax{1,3,5};
Run Code Online (Sandbox Code Playgroud)

我需要用

A ax{std::vector<int>{1,3,5}};
Run Code Online (Sandbox Code Playgroud)

而不是

for (auto x : ax) ...
Run Code Online (Sandbox Code Playgroud)

我需要用

for (auto x : (std::vector<int>)ax) ...
Run Code Online (Sandbox Code Playgroud)

Chr*_*ann 60

我认为你想要的仍然是最好的:

struct A : public std::vector<int>{
  using vector::vector;
};
struct B : public std::vector<int>{
  using vector::vector;
};
Run Code Online (Sandbox Code Playgroud)

它完全符合你的要求.为了避免干净的陈述,没有理由想出一些丑陋的hackery.我认为这种子类型不受欢迎的主要原因是相同的事物应该表现得像它们是相同的并且可以互换使用.但这正是你想要压缩的东西,因此它的子类型恰好就是你想要的语句:它们具有相同的接口,但它们不应该被使用,因为它们不相同.

  • @Aziuth:我们理解你在说什么.我们说你错了.`A`确实为向量添加了功能:作为`A`的功能.而已.问题解决了.没有更多的工作要做. (17认同)
  • @Aziuth恕我直言,这正是我们在编程中应该做的事情.充分利用类型检查器是一件非常好的事情,让我们发现许多错误,否则这些错误将被忽视. (12认同)
  • @Aziuth在具有更强类型系统的语言中实际上非常常见(OCaml浮现在脑海中).没有理由我可以将一个指定温度的整数传递给一个函数,该函数需要一个整数表示以分钟为单位的时间 - 它只是一个不重要的实现细节,它们都由相同的原始数据类型表示,但它们的含义是不同的,它们不应该是可以互换的. (11认同)
  • @Aziuth:vector不是多态的,所以无论派生类都不能作为向量替换,因为vector方法不会被重定向到继承的类.因此,任何与OOP相关的咒语(derive = is_a,如果它不是虚拟等都不得派生等),都不能在这里应用,因为我们不在纯OOP上下文中(我们在通用编程领域,这里) .A和B都衰变为向量的事实仅意味着采用向量的函数也可以与A和B一起工作(但可能是他需要的).但是需要A的函数不能用于B或向量. (6认同)
  • @Aziuth:不.类型`X`是类型`Y`的后代iff`X` _is_a_`Y`.如果其中任何一个_is_an_ other,则两种类型相同.OP明确指出`A`和`B`是不同的:`A` _is_not_a_`B`(即使你可以用相同的参数指定它们的状态,它们的原理图也不同). (3认同)
  • @Aziuth:当然你可以包装vector而不是inherit,然后通过简单的委托向量来重写它的所有方法.你会输入很多东西来添加任何东西并且支付相同的费用.如果矢量发生变化,您还必须更改您的课程,并且不满足DRY原则.所以这不好.你的条件是"不要从容器派生/不衍生出没有虚拟析构函数的东西"OOP咒语,这完全脱离了上下文.在这种情况下尊重它没有任何价值.这将是一件好事,乘以零. (3认同)
  • @Aziuth如果C++允许继承form int,我会建议.但继承添加任何内容,仅仅是为了区分类型,甚至是标准库在定义基于标签的调度时所做的事情.(请参阅如何声明迭代器标记以及SFINAE如何过滤它们).例如:http://www.boost.org/community/generic_programming.html#tag_dispatching (3认同)
  • @Aziuth如果这与"using"语句的唯一区别就是你要改变的东西,那么没有理由不使用它. (2认同)

Bar*_*rop 14

无论如何,这是一种原始的痴迷.无论是ints真的代表什么,vectors是这个东西的集合,或者vector<int>s代表某种东西.

在这两种情况下,都应该通过将原语包装成更有意义的东西来解决.例如:

class column
{
  int id;
  /*...*/
}; 
class row
{
  int id;
  /*...*/
};
Run Code Online (Sandbox Code Playgroud)

std::vector<row>并且std::vector<column>不可互换.

当然,相同的想法可以应用于,vector<int>而不是int,如果vector<int>原始真正意味着其他东西.

  • 我喜欢这个的第一部分,但不是第二部分.只需使用模板:`template <class T> class tagged_integer {/*...*/}; 使用row = tagged_integer <struct row_tag>;` (3认同)
  • 使用大量代码对基本积分类型进行子类型化以避免对向量本身进行子类型化只会移动问题.如果这是可以接受的,为什么不能为矢量子类型接受? (2认同)
  • @Christoph因为整数可能代表不同的东西.它们恰好存储在向量中.应该修复那种[原始的痴迷](https://sourcemaking.com/refactoring/smells/primitive-obsession). (2认同)
  • @lorro:API边界是一个特殊情况......但它们只是JUST边界.任何削减边界的东西都应该(1)验证并且(2)转换成适当类型的域...理想情况下,在一步中验证是关于建立不变量. (2认同)