繁体   English   中英

Clang模糊与自定义转换运算符

[英]Clang ambiguity with custom conversion operator

当我在clang下遇到问题时,我一直在开发一种适配器类。 如果定义了左值引用和右值引用的转换运算符,则会在尝试从类中移动时出现歧义编译错误(当此类代码应该没问题时,

operator const T& () const&

只适用于左手AFAIK)。 我用简单的例子重现了错误:

#include <string>

class StringDecorator
{
public:
  StringDecorator()
  : m_string( "String data here" )
  {}

  operator const std::string& () const& // lvalue only
  {
    return m_string;
  }

  operator std::string&& () && // rvalue only
  {
    return std::move( m_string );
  }

private:
    std::string m_string;
};

void func( const std::string& ) {}
void func( std::string&& ) {}

int main(int argc, char** argv)
{
  StringDecorator my_string;

  func( my_string ); // fine, operator std::string&& not allowed
  func( std::move( my_string ) ); // error "ambiguous function call"
}

在gcc 4.9+上编译正常,在任何clang版本上都失败。 所以问题是:有没有解决方法? 我对const& function修饰符的理解是对的吗?

PS:澄清 - 问题是关于修复StringDecorator类本身(或找到这样的类的解决方法,就好像是库代码)。 请不要直接提供调用操作符T &&()的答案或明确指定转换类型。

问题来自于选择最佳可行功能。 在第二个func调用的情况下,它意味着比较2个用户定义的转换序列 遗憾的是,如果不使用相同的用户定义转换函数或构造函数C ++标准 [over.ics.rank / 3],则无法区分 2个用户定义的转换序列

除非满足以下规则之一,否则相同形式的两个隐式转换序列是无法区分的转换序列:

  • [...]

  • 用户定义的转换序列U1是一个比另一个用户定义的转换序列U2更好的转换序列,如果它们包含相同的用户定义的转换函数或构造函数[...]

因为rvalue总是可以绑定到const lvalue引用,所以如果函数为const std::string&std::string&&重载,你将无论如何都会陷入这种模糊的调用。

正如您所提到的,我正在重新声明所有函数的第一个答案不是解决方案,因为您正在实现库。 实际上,不可能为所有以string作为参数的函数定义代理函数!

因此,让您在两个不完美的解决方案之间进行权衡:

  1. 你删除operator std::string&&() && ,你将失去一些优化,或;

  2. 您公开继承了std :: string,并删除了2个转换函数,在这种情况下,您将库暴露给滥用:

     #include <string> class StringDecorator : public std::string { public: StringDecorator() : std::string("String data here" ) {} }; void func( const std::string& ) {} void func( std::string&& ) {} int main(int argc, char** argv) { StringDecorator my_string; func( my_string ); // fine, operator std::string&& not allowed func( std::move( my_string )); // No more bug: //ranking of standard conversion sequence is fine-grained. } 

另一个解决方案是不使用Clang,因为它是Clang错误

但如果你必须使用Clang,Tony Frolov的答案就是解决方案。

Oliv的答案是正确的,因为在这种情况下标准似乎很清楚。 我一次选择的解决方案是只留下一个转换运算符:

operator const std::string& () const&

存在问题是因为两个转换运算符都被认为是可行的 因此,可以通过将lvalue转换运算符的隐式参数的类型从const&更改为来避免这种情况:

operator const std::string& () & // lvalue only (rvalue can't bind to non-const reference)
{
    return m_string;
}

operator std::string&& () && // rvalue only
{
    return std::move( m_string );
}

但这打破了const StringDecorator的转换,使其在典型情况下的使用变得尴尬。

这个破碎的解决方案让我想到是否有一种方法来指定成员函数限定符,它将使转换操作符对于const lvalue对象可行,但不能与rvalue一起使用。 我已经设法通过将const转换运算符的隐式参数指定为const volatile&来实现此目的:

operator const std::string& () const volatile& // lvalue only (rvalue can't bind to volatile reference)
{
    return const_cast< const StringDecorator* >( this )->m_string;
}

operator std::string&& () && // rvalue only
{
    return std::move( m_string );
}

Per [dcl.init.ref] / 5 ,对于通过绑定到rvalue初始化的引用,引用必须是const非易失性左值引用或rvalue引用:

而左值引用和const左值引用可以绑定到const volatile引用。 显然,成员函数上的volatile修饰符服务完全不同。 但是,嘿,它对我的​​用例来说是有效的。 唯一剩下的问题是代码变得误导和惊人。

clang ++更准确。 两个func重载都不是StringDecorator const&StringDecorator&&完全匹配。 因此无法移动my_string 编译器无法在可能的转换之间进行选择StringDecorator&& - > std::string&& - > func(std::string&&)StringDecorator&& - > StringDecorator& - > std::string& - > func(const std::string&) 换句话说,编译器无法确定应该在哪个步骤应用强制转换运算符。

我没有安装g ++来检查我的假设。 我想这是第二种方式,因为无法移动my_string ,它将转换运算符const&应用于StringDecorator& 如果将调试输出添加到强制转换运算符的主体中,则可以检查它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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