使用自定义转换运算符

时间:2017-12-05 16:35:55

标签: c++ c++11 decorator move

当我在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&amp; 函数修饰符的理解是对吗?

P.S。:澄清 - 问题是关于修复StringDecorator类本身(或找到这样的类的解决方法,就好像是库代码一样)。请不要直接提供直接调用运营商T&amp;&amp;()的答案,也不要明确指定转换类型。

3 个答案:

答案 0 :(得分:3)

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

  

相同形式的两个隐式转换序列是不可区分的转换序列,除非其中之一   以下规则适用:

     
      
  • [...]

  •   
  • 用户定义的转换序列   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.
    }
    
  3. 另一种解决方案是不使用Clang,因为它是bug of Clang

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

答案 1 :(得分:2)

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

operator const std::string& () const&

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

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&amp; 来设法实现这一点:

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

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

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

答案 2 :(得分:1)

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

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

相关问题