I need to extend std::basic_string
to work over path strings and different 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;
}
At the https://godbolt.org/z/jhcWoh i've got these errors:
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 (with --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
I know at least one workaround for that.
But the hell is what even happened? What in the ridiculousness i have to overload again additionally to avoid that overload collision mess?
Update:
Fixed by removing const
and single reference
from all basic_string
kind arguments of all operator+
. Seems that works it out.
First things first, use move-from-value instead of
const&
and&&
overloads.and get rid of
base_type const&
ctor.Second, make that ctor explicit:
as a path is a different thing than a string.
Third, clean up your
template operator+
and make it an ADL "Koenig" operator that takes its left hand side by value. Oh, and don't return anything by rvalue reference, that is toxic.and get rid of all that noise.
Next, inherit ctors from
base_type
.Finally, implement appending using
+=
and make the operations symmetric:operator+
here is fancy as it is only findable via ADL, yet it actually operates on the base type of the class.This means that at least one of the arguments must be an instance of this type (or have an instance of this type as a template argument) in order for it to be found.
Then conversion-to-base occurs if required.
I take LHS by value, because moving a string is cheap-to-free, and we need a string for output. By taking LHS by value and using its buffer (after moving it) for the return value, we get efficient chained addition:
becomes
now the return value of
a+b
(a prvalue) is then used as the lhs argument of(a+b)+c
.This recycling of the buffer continues; only one buffer is created (from the first
+
), and it is then moved, resized (hopefully efficiently) and reused for the rest of the expression.