Совместное использование реализаций для перегрузок const lvalue (const T&) и rvalue (T&&): точно так же, как это делается для константных и неконстантных перегрузок

Фон

Следующий блок кода появляется в известной книге Скотта Мейерса "Эффективный C++", пункт 3:

class TextBlock {
public:
    ...
    const char& operator[](std::size_t position) const
    {
        ...    // do bounds checking
        ...    // log access data
        ...    // verify data integrity
        return text[position];
    }
    char& operator[](std::size_t position)
    {
        ...    // do bounds checking
        ...    // log access data
        ...    // verify data integrity
        return text[position];
    }
    ...
private:
    std::string text;
};

Автор заявляет, что в приведенной выше реализации содержание const и не const перегрузки по сути одинаковы. Чтобы избежать дублирования кода, его можно упростить следующим образом:

class TextBlock {
public:
    ...
    const char& operator[](std::size_t position) const    // the same as before
    {
        ...
        ...
        ...
        return text[position];
    }
    char& operator[](std::size_t position)        // now just calls const op[]
    {
        return                                    // cast away const on
          const_cast<char&>(                      // op[]'s return type;
            static_cast<const TextBlock&>(*this)  // add const to *this's type;
              [position]                          // call const version of op[]
          );
    }
    ...
private:
    std::string text;
};

Вопросы

Мои вопросы:

  • Когда нам понадобится перегрузка для const T& и еще один для T&&? (Вот, T может быть параметром шаблона или типом класса, так T&& может означать или не означать универсальную ссылку) Я вижу, что в стандартной библиотеке многие классы обеспечивают обе перегрузки. Примерами являются конструкторы std::pair а также std::tuple Есть тонны перегрузок. (Хорошо, я знаю, что среди функций одна из них - конструктор копирования, а одна - конструктор перемещения.)

  • Есть ли похожий трюк, чтобы поделиться реализациями для const T& а также T&& Перегрузки? Я имею в виду, если const T&& Перегрузка возвращает объект, созданный копией, и T&& Перегрузка возвращает что-то, что было сконструировано с перемещением, после совместного использования реализации это свойство все равно должно храниться. (Так же, как вышеупомянутый трюк: const возвращается const и не const возвращает не const как до, так и после реализации обмена)

Спасибо!

Разъяснения

Две перегрузки, о которых я говорю, должны выглядеть следующим образом:

Gadget f(Widget const& w);
Gadget f(Widget&& w);

Это не связано с возвратом по ссылкам rvalue, то есть:

Widget&& g(/* ... */);

(Кстати, этот вопрос был рассмотрен в моем предыдущем посте)

В f() выше, если Gadget является как копируемым, так и перемещаемым, нет никакого способа (кроме чтения реализации) определить, является ли возвращаемое значение созданным с помощью копирования или созданным с помощью перемещения. Нет ничего общего с оптимизацией возвращаемого значения (RVO) / оптимизацией именованного возврата (NRVO). (См. Мой предыдущий пост)

Рекомендации

Эффективный C++

станд:: пары:: пара

станд:: кортеж:: кортеж

Когда лучше возвращаться по ссылкам?

1 ответ

• Когда нам понадобится перегрузка для const T& и другая для T&&?

По сути, когда перемещение дает вам прирост производительности, должен быть и конструктор перемещения. То же самое относится к функциям, в которых в противном случае вам понадобилась бы дорогая копия.

В вашем примере, где вы возвращаете ссылку на charОднако не рекомендуется также устанавливать функцию, которая возвращает ссылку на значение. Скорее, возвращайте по значению и полагайтесь на способность компилятора применять RVO (см., Например, здесь)

• Есть ли подобный прием для совместного использования реализаций для постоянных перегрузок T & и T&&?

Я часто находил полезным установить конструктор или функцию, используя универсальную ссылку (я ленивый), то есть что-то вроде

struct MyClass
{
    template<typename T /*, here possibly use SFINAE to allow only for certain types */>
    MyClass(T&& _t) : t(std::forward<T>(_t)) {}
private:
    SomeType t;
};

РЕДАКТИРОВАТЬ: Относительно вашего обновления: если у вас есть дорогая копия Widget в вашей функции fРекомендуется также обеспечить перегрузку, принимая Widget&&,

Gadget f(Widget const& w)
{
    Widget temp = w;  //expensive copy
}
Gadget f(Widget&& w)
{
    Widget temp = std::move(w);  //move
}

Вы можете объединить обе функции, используя такой шаблон функции

template<typename WidgetType
       // possibly drop that SFINAE stuff
       // (as it is already checked in the first assignment)
       , typename std::enable_if<std::is_convertible<std::remove_reference_t<WidgetType>, Widget>::value> >
Gadget(WidgetType&& w)
{
    Widget temp = std::forward<WidgetType>(w);
    //or
    std::remove_reference_t<WidgetType> temp2 = std::forward<WidgetType>(w);
}

... Я не сказал, что это лучше;-).


РЕДАКТИРОВАТЬ 2: См. Также эту ветку, которая рассматривает ваш вопрос гораздо более тщательно.

Другие вопросы по тегам