【问题标题】:std::string class inheritance and tedious c++ overload resolution #2std::string 类继承和繁琐的 c++ 重载解析 #2
【发布时间】:2019-04-10 02:22:31
【问题描述】:

上一个问题:std::string class inheritance and tedious c++ overload resolution

在上一个问题之后的步骤中,我尝试通过原始字符串指针测试operator+"aaa" + path_string{ "bbb" }。发现并没有调用相应的path_string类友元函数。

我尝试添加不模板重载operator+(2),但它也没有工作。但我发现模板化的 (3) 确实有效。

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

3 个编译器的输出:gcc 5.4、clang 3.8.0、msvc 2015 (19.00.23506)

-333/bbb-

https://rextester.com/BOFUS59590

我记得 C++ 标准对此进行了澄清,因为只有当没有一个非模板化函数与参数完全匹配时,才必须查找模板化函数。但是 (2) 运算符必须完全匹配,但为什么它甚至没有被调用?

如果删除(3),则(1) 将调用而不是(2),后者比(1) 匹配更好。

这是怎么回事?

PS:我认为这与 const + single reference 的问题相同,就像上一个问题一样。

【问题讨论】:

  • 请注意std::move(std::forward&lt;T&gt;(...)) 是多余的,std::move(...) 应该给出相同的结果。无论哪种方式,您总是会得到一个右值引用。在您的运营商的上下文中,std::movestd::forward 都不做任何事情。我不清楚混淆的确切位置,但您误解了移动语义的工作原理。
  • @FrançoisAndrieux std::forward 将引用转换为对不同类型(在本例中为基类)的引用,然后std::move 将转发引用更改为右值引用,是吗?可能只是static_cast&lt;base_type &amp;&amp;&gt;(r)
  • @bipll 我不确定你在问什么。但是base_type 从来都不是引用类型,所以std::forward 不会执行任何完美转发(T 被完全忽略)。添加std::move 保证,您将通过始终移动(如果不能编译则无法编译)来忽略完美转发。编辑:要回复对您的评论的编辑,该演员基本上只是std::move
  • 我的问题是why (3) calls instead of (2)?
  • 什么是对引用的引用?

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


【解决方案1】:

在下面的sn-p中:

std::string test_path_string_operator_plus_right_xref(path_string && right_path_str)
{
    return "aaa" + right_path_str;
}

right_path_str 是一个非常量左值。因此,它永远不能绑定到右值引用。

当模板重载不可用时,它会绑定到 const 左值引用重载

friend path_basic_string operator+ (const t_elem * p, const base_type & r)

当模板存在时,它更适合非 const 左值引用:

template <typename T>
friend path_basic_string operator+ (const t_elem * p, T && r)

在这种情况下,T&amp;&amp; 是一个转发引用,它折叠为非常量左值引用。要修复您的代码,请确保在调用函数时从右值引用参数move 并养成习惯 - 每当您在下面传递右值引用时,使用std::move,并且每当您传递转发引用时,使用std::forward

std::string test_path_string_operator_plus_right_xref(path_string && right_path_str)
{
    return "aaa" + std::move(right_path_str);
}

【讨论】:

  • 如果我理解正确,模板化的T &amp;&amp; 可以绑定到const blabla &amp;blabla &amp;&amp;,而blabla &amp;&amp; 只能绑定到blabla &amp;&amp;。对吗?
  • @Andry 模板化的T&amp;&amp; 可以绑定到任何东西(关键字:转发引用)。它与path_string&amp;&amp; 不同,后者仅绑定到临时对象。
  • @MaxLanghof 为什么它不绑定到自身?函数参数类型为path_string &amp;&amp;
  • 函数参数不是类型path_string&amp;&amp;,它绑定到类型path_string&amp;&amp;。参数本身是一个左值,因为它有一个名称。是的,这种区别一开始很令人困惑。
  • @Andry 一个左值永远不能绑定到右值引用。时期。在Foo&amp;&amp; f 中,f 是左值,除非 Foo 是函数的模板参数。重申:右值引用是左值!
猜你喜欢
  • 2023-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-05
相关资源
最近更新 更多