std :: string类继承和繁琐的c ++重载解析

时间:2018-11-05 13:09:56

标签: c++ c++11 gcc visual-studio-2015 gcc5

我需要扩展std::basic_string来处理路径字符串和其他operator+

#include <string>

template <class t_elem, class t_traits, class t_alloc>
class path_basic_string : public std::basic_string<t_elem, t_traits, t_alloc>
{
public:
    using base_type = std::basic_string<t_elem, t_traits, t_alloc>;

    path_basic_string() = default;
    path_basic_string(const path_basic_string & ) = default;
    path_basic_string & operator =(const path_basic_string &) = default;

    path_basic_string(const base_type & r) :
        base_type(r)
    {
    }

    path_basic_string(base_type && r) :
        base_type(std::move(r))
    {
    }
};

using path_string = path_basic_string<char, std::char_traits<char>, std::allocator<char> >;

template <class t_elem, class t_traits, class t_alloc>
inline path_basic_string<t_elem, t_traits, t_alloc> &&
    operator +(
        path_basic_string<t_elem, t_traits, t_alloc> && l,
        std::basic_string<t_elem, t_traits, t_alloc> && r)
{
    std::basic_string<t_elem, t_traits, t_alloc> && l_str = std::move(l);
    std::basic_string<t_elem, t_traits, t_alloc> && r_str = std::move(r);

    const bool has_right = !r_str.empty();
    return std::move(
        path_basic_string<t_elem, t_traits, t_alloc>{
            std::move(std::move(l_str) + (has_right ? "/" : "") + (has_right ? std::move(r_str) : std::move(std::basic_string<t_elem, t_traits, t_alloc>{})))
        });
}

template <class t_elem, class t_traits, class t_alloc>
inline path_basic_string<t_elem, t_traits, t_alloc>
    operator +(
        const path_basic_string<t_elem, t_traits, t_alloc> & l,
        const std::basic_string<t_elem, t_traits, t_alloc> & r)
{
    const std::basic_string<t_elem, t_traits, t_alloc> & l_str = l;

    const bool has_right = !r.empty();
    return path_basic_string<t_elem, t_traits, t_alloc>{
        l_str + (has_right ? "/" : "") + (has_right ? r : std::basic_string<t_elem, t_traits, t_alloc>{})
    };
}

int main()
{
    path_string a;
    std::string b;
    std::string c;
    const path_string test = a + (b + c);

    return 0;
}

https://godbolt.org/z/jhcWoh,我遇到了以下错误:

x86 MSVC 19 2015 U3

/opt/compiler-explorer/windows/19.00.24210/include/xlocale(341):
warning C4530: C++ exception handler used, but unwind semantics are
not enabled. Specify /EHsc

<source>(61): error C2666: 'operator +': 3 overloads have similar
conversions

<source>(44): note: could be
'path_basic_string<char,std::char_traits<char>,std::allocator<char>>
operator +<char,std::char_traits<char>,std::allocator<char>>(const
path_basic_string<char,std::char_traits<char>,std::allocator<char>>
&,const
std::basic_string<char,std::char_traits<char>,std::allocator<char>>
&)'

<source>(28): note: or      
'path_basic_string<char,std::char_traits<char>,std::allocator<char>>
&&operator
+<char,std::char_traits<char>,std::allocator<char>>(path_basic_string<char,std::char_traits<char>,std::allocator<char>> &&,std::basic_string<char,std::char_traits<char>,std::allocator<char>>
&&)'

/opt/compiler-explorer/windows/19.00.24210/include/xstring(2310):
note: or      
'std::basic_string<char,std::char_traits<char>,std::allocator<char>>
std::operator
+<char,std::char_traits<char>,std::allocator<char>>(const std::basic_string<char,std::char_traits<char>,std::allocator<char>>
&,const
std::basic_string<char,std::char_traits<char>,std::allocator<char>>
&)'

/opt/compiler-explorer/windows/19.00.24210/include/xstring(2380):
note: or      
'std::basic_string<char,std::char_traits<char>,std::allocator<char>>
std::operator
+<char,std::char_traits<char>,std::allocator<char>>(const std::basic_string<char,std::char_traits<char>,std::allocator<char>>
&,std::basic_string<char,std::char_traits<char>,std::allocator<char>>
&&)'

/opt/compiler-explorer/windows/19.00.24210/include/xstring(2390):
note: or      
'std::basic_string<char,std::char_traits<char>,std::allocator<char>>
std::operator
+<char,std::char_traits<char>,std::allocator<char>>(std::basic_string<char,std::char_traits<char>,std::allocator<char>> &&,const
std::basic_string<char,std::char_traits<char>,std::allocator<char>>
&)'

/opt/compiler-explorer/windows/19.00.24210/include/xstring(2400):
note: or      
'std::basic_string<char,std::char_traits<char>,std::allocator<char>>
std::operator
+<char,std::char_traits<char>,std::allocator<char>>(std::basic_string<char,std::char_traits<char>,std::allocator<char>> &&,std::basic_string<char,std::char_traits<char>,std::allocator<char>>
&&)'

<source>(61): note: while trying to match the argument list
'(path_string,
std::basic_string<char,std::char_traits<char>,std::allocator<char>>)'

<source>(61): note: note: qualification adjustment (const/volatile)
may be causing the ambiguity

Compiler returned: 2

x86-64 gcc 5.4 (带有--std=c++11):

source>: In function 'int main()':

<source>:61:40: warning: ISO C++ says that these are ambiguous, even
though the worst conversion for the first is better than the worst
conversion for the second:

     const path_string test = a + (b + c);

                                        ^

<source>:44:5: note: candidate 1: path_basic_string<t_elem, t_traits,
t_alloc> operator+(const path_basic_string<t_elem, t_traits,
t_alloc>&, const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&)
[with t_elem = char; t_traits = std::char_traits<char>; t_alloc =
std::allocator<char>]

     operator +(

     ^

In file included from
/opt/compiler-explorer/gcc-5.4.0/include/c++/5.4.0/string:52:0,

                 from <source>:1:

/opt/compiler-explorer/gcc-5.4.0/include/c++/5.4.0/bits/basic_string.h:4854:5:
note: candidate 2: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>
std::operator+(const std::__cxx11::basic_string<_CharT, _Traits,
_Alloc>&, std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]

     operator+(const basic_string<_CharT, _Traits, _Alloc>& __lhs,

     ^

Compiler returned: 0

我知道至少有一种解决方法。

但是到底发生了什么? 为了避免这种过载冲突的混乱,我又必须再次超载吗?

更新: 通过从所有const的所有single reference类参数中删除basic_stringoperator+来解决。似乎可以解决问题。

1 个答案:

答案 0 :(得分:2)

首先,请使用从值移动而不是const&&&重载。

path_basic_string(base_type r) :
    base_type(std::move(r))
{
}

并摆脱base_type const& ctor。

第二,使该ctor明确:

explicit path_basic_string(base_type r) :
    base_type(std::move(r))
{
}

因为路径与字符串是不同的东西。

第三,清理template operator+并将其设置为ADL“ Koenig”运算符,该运算符将其值作为左手。哦,不要通过右值引用返回任何有毒的东西。

friend path_basic_string
    operator +(
        path_basic_string l,
        base_type const& r)
{
  base_type& l_str = l;
  if (!r.empty())
    l = path_basic_string( std::move(l_str) + "/" + r );
  return l;
}

并消除所有杂音。

接下来,从base_type继承ctor。

最后,使用+=实现附加,并使操作对称:

template <class t_elem, class t_traits, class t_alloc>
class path_basic_string : public std::basic_string<t_elem, t_traits, t_alloc>
{
public:
    using base_type = std::basic_string<t_elem, t_traits, t_alloc>;

    path_basic_string() = default;
    path_basic_string(const path_basic_string & ) = default;
    path_basic_string & operator =(const path_basic_string &) = default;

    using base_type::base_type;

    explicit path_basic_string(base_type r) :
        base_type(std::move(r))
    {
    }
    path_basic_string& operator+= ( base_type const& rhs ) & {
      if (!rhs.empty())
      {
        base_type& self = *this;
        self += '/';
        self += rhs;
      }
      return *this;
    }
    friend path_basic_string operator+(
            base_type l,
            base_type const& r
    )
    {
      path_basic_string l_path(std::move(l));
      l+=r;
      return l;
    }
};

operator+之所以如此,是因为它只能通过ADL找到,但实际上它是基于该类的 base 类型进行操作。

这意味着至少有一个参数必须是此类型的实例(或具有此类型的实例作为模板参数)才能找到它。

然后根据需要进行基础转换。

我将LHS视为一个值,因为移动字符串很便宜,而且我们需要一个字符串才能输出。通过将LHS按值取值并使用其缓冲区(将其移动后)作为返回值,我们可以获得有效的链式加法:

a+b+c+d+e

成为

(a+b)+c+d+e

现在将a+b(一个prvalue)的返回值用作(a+b)+c的lhs参数。

此缓冲区的回收继续进行; (从第一个+开始,仅创建一个缓冲区,然后将其移动,调整大小(希望有效)并在表达式的其余部分重新使用。