std :: string类继承和繁琐的c ++重载决策

And*_*dry 7 c++ gcc c++11 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;
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

但到底发生了什么呢?什么在荒谬中我还要再次超载以避免过载碰撞乱?

更新:通过消除固定constsingle reference所有basic_string所有的实物论据operator+.似乎有效.

Yak*_*ont 2

首先,使用 move-from-value 而不是const&and&&重载。

path_basic_string(base_type r) :
    base_type(std::move(r))
{
}
Run Code Online (Sandbox Code Playgroud)

并摆脱base_type const&演员。

其次,使该 ctor 明确:

explicit path_basic_string(base_type r) :
    base_type(std::move(r))
{
}
Run Code Online (Sandbox Code Playgroud)

因为路径与字符串不同。

第三,清理您的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;
}
Run Code Online (Sandbox Code Playgroud)

并消除所有噪音。

接下来,从 继承ctors base_type

最后,使用追加操作+=并使操作对称:

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;
    }
};
Run Code Online (Sandbox Code Playgroud)

operator+这里很奇特,因为它只能通过 ADL 找到,但它实际上是在类的类型上运行的。

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

然后如果需要的话会发生转换为基数。

我按值取 LHS,因为移动字符串是便宜到免费的,而且我们需要一个字符串用于输出。通过按值获取 LHS 并使用其缓冲区(移动后)作为返回值,我们得到了高效的链式加法:

a+b+c+d+e
Run Code Online (Sandbox Code Playgroud)

变成

(a+b)+c+d+e
Run Code Online (Sandbox Code Playgroud)

现在 (a prvalue) 的返回值a+b被用作 的 lhs 参数(a+b)+c

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