【问题标题】:Creating a compatible String object创建兼容的 String 对象
【发布时间】:2017-09-17 05:52:47
【问题描述】:

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

它像这样隐式转换 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);
}

如果我用上面的TypeIDoNotOwn 替换TypeIOwn,它会编译。如何在不修改TypeIDoNotOwn 的情况下使用TypeIOwn 编译它?并且无需在声明时引入除类型更改之外的任何强制转换或更改?

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

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*) {}
       ^

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

实际问题的范围很大;数百万行代码,我想在数千个位置将 TypeIDoNotOwn 换成 TypeIOwn,而不是在数百个其他位置。在数千个位置,它们以一种导致转换模糊的方式进行交互。

我已经解决了一个函数在它发生的 100 个点上使用TypeIDoNotOwn&amp; 的问题,方法是用一个宏包装它,该宏创建一个临时对象,该对象从TypeIOwn 创建一个TypeIDoNotOwn,返回一个引用然后,当临时对象被销毁时,将其复制回TypeIOwn。我想避免必须进行类似的扫描来处理==+==、复制构造和类似情况。

Live example.

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

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

【问题讨论】:

  • 我担心如果不修改使用类型(此处为main)或您不拥有的类型的代码,这将不会成功.. 可惜我们没有免费的(非成员)用户定义的转换运算符;与他们一起,可能会利用 ADL 来解决这种歧义。
  • 注意:提供到char const (&amp;)[SIZE] 的转换不起作用(初始化和TypeIDoNotOwn 构造的衰减相同)。
  • struct TypeIDoOwn : TypeIDoNotOwn?

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


【解决方案1】:

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

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

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

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

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

您必须为每次更新都进行测试,直到您最终尝试。

既然你说这是一个字符串类,还要考虑转向std::string的选项,这样你的TypeIOwn就变成了std::string的瘦包装器,不再提供到const char*的隐式转换。相反,提供 data()。您不再有从 TypeIOwn -> (const char* | TypeIDoNotOwn) -> TypeIDoNotOwn 的模棱两可的转换,因为像 std::string 一样,您不再允许隐式转换为 const char*,以及您为使代码工作而投入的任何工作当您完全放弃两个字符串类并使用 std::string 时,这将得到回报。

【讨论】:

    【解决方案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 构造都可以与一个转换函数一起正常工作。但仅仅通过消除过程,我们不能两者兼得。

    【讨论】:

    • 嗯。 TypeIDoNotOwn 实际上有一个父类型。或者我可以定义一个派生自TypeIDoNotOwn 的类型,它可以被分割成TypeIDoNotOwn。我将不得不进行实验。
    猜你喜欢
    • 1970-01-01
    • 2022-11-14
    • 2016-08-11
    • 1970-01-01
    • 1970-01-01
    • 2011-06-06
    • 2014-11-26
    • 1970-01-01
    • 2023-04-03
    相关资源
    最近更新 更多