Previous question: std::string class inheritance and tedious c++ overload resolution
In steps after the previous question i have tried to test operator+
over the raw string pointer: "aaa" + path_string{ "bbb" }
. And found it does not call the respective path_string
class friend function.
I tried to add not template overload operator+
(2)
but it didn't work either. But i've found that the templated one (3)
did work.
#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(path_basic_string &&) = default;
path_basic_string & operator =(path_basic_string path_str)
{
this->base_type::operator=(std::move(path_str));
return *this;
}
path_basic_string(base_type r) :
base_type(std::move(r))
{
}
path_basic_string(const t_elem * p) :
base_type(p)
{
}
base_type & str()
{
return *this;
}
const base_type & str() const
{
return *this;
}
using base_type::base_type;
using base_type::operator=;
// ... all over operators are removed as not related to the issue ...
// (1)
friend path_basic_string operator+ (const t_elem * p, const base_type & r)
{
path_basic_string l_path = p;
l_path += "xxx";
return std::move(l_path);
}
friend path_basic_string operator+ (const t_elem * p, base_type && r)
{
if (!r.empty()) {
return "111" + ("/" + r); // call base operator instead in case if it is specialized for this
}
return "111";
}
// (2)
friend path_basic_string operator+ (const t_elem * p, path_basic_string && r)
{
base_type && r_path = std::move(std::forward<base_type>(r));
if (!r_path.empty()) {
return "222" + ("/" + r_path); // call base operator instead in case if it is specialized for this
}
return "222";
}
// (3) required here to intercept the second argument
template <typename T>
friend path_basic_string operator+ (const t_elem * p, T && r)
{
base_type && r_path = std::move(std::forward<base_type>(r));
if (!r_path.empty()) {
return "333" + ("/" + r_path); // call base operator instead in case if it is specialized for this
}
return "333";
}
};
using path_string = path_basic_string<char, std::char_traits<char>, std::allocator<char> >;
std::string test_path_string_operator_plus_right_xref(path_string && right_path_str)
{
return "aaa" + right_path_str;
}
int main()
{
const path_string test =
test_path_string_operator_plus_right_xref(std::move(path_string{ "bbb" }));
printf("-%s-\n", test.str().c_str());
return 0;
}
The output for 3 compilers: gcc 5.4, clang 3.8.0, msvc 2015 (19.00.23506)
-333/bbb-
https://rextester.com/BOFUS59590
As i remembered the C++ standard clarifies this like as a templated function has to be lookup only when no one not templated function is matched arguments exactly. But the (2)
operator has to be matched exactly, but why then it is not even called?
If remove (3)
then the (1)
would call instead of (2)
which is matches better than (1)
.
What is going on here?
PS: I think this is the same issue around the const
+ single reference
like from previous question.
In following snippet:
right_path_str
is a non-const l-value. Because of that, it can never bind to an rvalue reference.When template overload is not available, it binds to const lvalue reference overload in
and when template is there, it's a better match for non-const lvalue reference:
In this case,
T&&
is a forwarding reference, which collapses to non-const lvalue reference. To fix your code, make sure tomove
from rvalue reference arguments when calling functions and make it a habit - whenever you pass an rvalue reference below, usestd::move
, and whenever you pass forwarding reference, usestd::forward
.