创建兼容的String对象

时间:2017-04-20 14:20:33

标签: c++ c++14 assignment-operator c++17 ambiguous

所以我有一个提供字符串类型的现有库。

它隐式转换为C样式字符串,如下所示:

struct TypeIDoNotOwn {
  TypeIDoNotOwn() {}
  TypeIDoNotOwn(TypeIDoNotOwn const&) {}
  TypeIDoNotOwn(char const*) {}

  TypeIDoNotOwn& operator=(TypeIDoNotOwn const&) {return *this;}
  TypeIDoNotOwn& operator=(char const*) {return *this;}

  operator char const*() const {return nullptr;}
};

它有其他方法,但我不认为它们很重要。这些方法有实体,但我的问题并不涉及它们,所以我把它们剔除了。

我想要做的是创建一个可以与上述类型相对可互换使用的新类型,以及"raw string constants"。我希望能够获取TypeIDoNotOwn的实例,并将其替换为TypeIDoOwn,并编译代码。

作为一个例子,这组操作:

void test( TypeIDoNotOwn const& x ) {}

int main() {
  TypeIOwn a = TypeIDoNotOwn();
  TypeIDoNotOwn b;
  a = b;
  b = a;
  TypeIOwn c = "hello";
  TypeIDoNotOwn d = c;
  a = "world";
  d = "world";
  char const* e = a;
  std::pair<TypeIDoNotOwn, TypeIDoNotOwn> f = std::make_pair( TypeIOwn(), TypeIOwn() );
  std::pair<TypeIOwn, TypeIOwn> g = std::make_pair( TypeIDoNotOwn(), TypeIDoNotOwn() );
  test(a);
}

如果我将TypeIOwn替换为上面的TypeIDoNotOwn,则会进行编译。如何在不使用修改TypeIOwn的情况下使用TypeIDoNotOwn 进行编译?并且没有必须在声明点引入除了类型更改之外的任何演员表或更改?

我的第一次尝试看起来有点像这样:

struct TypeIOwn {
  TypeIOwn() {}
  operator char const*() const {return nullptr;}
  operator TypeIDoNotOwn() const {return {};}
  TypeIOwn( TypeIOwn const& ) {}
  TypeIOwn( char const* ) {}
  TypeIOwn( TypeIDoNotOwn const& ) {}
  TypeIOwn& operator=( char const* ) {return *this;}
  TypeIOwn& operator=( TypeIOwn const& ) {return *this;}
  TypeIOwn& operator=( TypeIDoNotOwn const& ) {return *this;}
};

但是我得到了一系列模糊的重载:

 main.cpp:31:4: error: use of overloaded operator '=' is ambiguous (with operand types 'TypeIDoNotOwn' and 'TypeIOwn')
         b = a;
         ~ ^ ~
 main.cpp:9:17: note: candidate function
         TypeIDoNotOwn& operator=(TypeIDoNotOwn const&) {return *this;}
                        ^
 main.cpp:10:17: note: candidate function
         TypeIDoNotOwn& operator=(char const*) {return *this;}

 /usr/include/c++/v1/utility:315:15: error: call to constructor of 'TypeIDoNotOwn' is ambiguous
             : first(_VSTD::forward<_U1>(__p.first)),
               ^     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 main.cpp:40:51: note: in instantiation of function template specialization 'std::__1::pair<TypeIDoNotOwn, TypeIDoNotOwn>::pair<TypeIOwn, TypeIOwn>' requested here
       std::pair<TypeIDoNotOwn, TypeIDoNotOwn> f = std::make_pair( TypeIOwn(), TypeIOwn() );
                                                   ^
 main.cpp:7:7: note: candidate constructor
       TypeIDoNotOwn(TypeIDoNotOwn const&) {}
       ^
 main.cpp:8:7: note: candidate constructor
       TypeIDoNotOwn(char const*) {}
       ^

在我的“真实”代码中,我有其他运算符,例如+===,它们有类似的问题。

真正问题的范围很大;数以百万计的代码行,我想在数千个位置换掉TypeIDo的TypeIDoNotOwn,而不是数百个其他位置。在数千个地点,他们以导致转换模糊的方式进行互动。

我已经解决了一个函数问题,即函数在100个点上发生TypeIDoNotOwn&,它通过一个宏包装它来创建一个临时对象,从TypeIDoNotOwn创建一个TypeIOwn返回对该引用的引用,然后在销毁临时对象时将其复制回TypeIOwn。我希望避免执行类似的扫描来处理==+==,复制构造和类似情况。

Live example

如果我尝试删除operator TypeIDoNotOwn以消除这种歧义,那么转换需要发生的其他情况就无法正常工作(因为它需要2个用户定义的结构才能从TypeIOwn转到TypeIDoNotOwn),然后需要进行显式转换(在100多个或1000个位置)

如果我可以让一个转换看起来比另一个更糟糕,它会起作用。如果做不到这一点,我可以通过重载一个具有完全匹配的免费operator=运算符(和其他情况类似)来修复非TypeIDoNotOwn == TypeIOwn和复制构造案例,但这不会让我构建,函数调用和赋值。

2 个答案:

答案 0 :(得分:2)

通常需要注意的是这是C ++,并且必然会有一些聪明的解决方法......不。

让我们来看看你的用例。您希望复制初始化和复制分配都能正常工作:

TypeIOwn a = ...;
TypeIDoNotOwn b = a;  // (*)
TypeIDoNotOwn c;
c = a;                // (*)

这需要:

operator TypeIDoNotOwn();

如果您刚刚提供了operator const char*(),那么分配将起作用,但复制初始化将失败。如果你提供了两者,那就不明确了,因为没有办法强迫一个转换优先于另一个转换(强制转换排序的唯一真正方法是创建类型层次结构,但你不能继承const char*所以你不能真的强迫它工作)。

一旦我们开始只使用一个转换函数,从示例列表中无效的唯一代码是:

const char* e = a; // error: no viable conversion

此时,您必须添加成员函数:

const char* e = a.c_str();

两个pair构造都适用于一个转换函数。但只是通过消除过程,我们不能同时拥有两者。

答案 1 :(得分:1)

没有灵丹妙药,但你可以通过声明从TypeIOwn到TypeIDoNotOwn的转换为显式来获得一些改进。

explicit operator TypeIDoNotOwn() const { return{}; }

这意味着您必须在发生这种情况的每个位置进行更改,但它确实解决了问题&#34; const char *&#34;对作业同样有效。是值得的权衡吗?你必须做出决定。

然而,为了逐步改变代码库,我在使用不同策略的类似情况下运气不错。我只是设置一个#define标志并使用完全一个或另一个进行编译,我可以继续使用TypeIDoNotOwn进行正常编码,同时在使所有内容与TypeIDoOwn一起工作方面取得进展。

#ifdef SOME_FLAG
struct TypeIOwn {...};
typedef TypeIOwn TypeIDoNotOwn;
#else
struct TypeIDoNotOwn {...};
#endif

您必须为每次更新测试两者,直到最终完成。

因为你说这是一个字符串类,所以也考虑转向std :: string的选项,所以你的TypeIOwn成为std :: string的瘦包装器,不再提供对const char *的隐式转换。相反,提供data()。您不再需要来自TypeIOwn的模糊转换 - &gt; (const char * | TypeIDoNotOwn) - &gt; TypeIDoNotOwn,因为像std :: string一样,你不再允许隐式转换为const char *,并且当你完全抛弃两个字符串类并使用std :: string时,你付出的任何工作都会得到回报。 / p>