Наследование класса std :: string и утомительное решение C ++ для перегрузки # 2 - PullRequest
0 голосов
/ 06 ноября 2018

Предыдущий вопрос: std :: наследование строковых классов и утомительное разрешение перегрузки c ++

В шагах после предыдущего вопроса я попытался проверить 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, как и в предыдущем вопросе.

1 Ответ

0 голосов
/ 06 ноября 2018

В следующем фрагменте:

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

right_path_str - неконстантное l-значение. Из-за этого он никогда не может привязываться к rvalue-ссылке.

Если шаблонная перегрузка недоступна, она связывается с постоянной перегрузкой ссылки в lvalue в

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

и когда шаблон есть, он лучше подходит для неконстантной ссылки на lvalue:

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

В этом случае T&& является ссылкой для пересылки, которая сворачивается в ссылку на неконстантное значение. Чтобы исправить свой код, обязательно move из аргументов ссылки rvalue при вызове функций и сделайте это привычкой - всякий раз, когда вы передаете ссылку rvalue ниже, используйте 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);
}
...