Автоматически реализовать оператор+, используя ЦРТП


У меня есть несколько классов, которые должны поддерживать дополнительно. Для каждого из них, оператор += реализуется как T& operator+=(const T& rhs). Я также хочу добавить оператор +, но эта реализация будет использовать += и, следовательно, будет похож во всех классах. Моя идея заключалась в том, чтобы реализовать шаблон класса, используя любопытно повторяющийся шаблон рисунка , что обеспечивает реализацию. В результате такой код:

template<class T>
class Sumable {
private:
    Sumable() = default;

public:
    Sumable(const Sumable<T>&) = delete;
    Sumable(Sumable<T>&&) = delete;

    T operator+(const T&) const&;
    T operator+(const T&) && ;
    T operator+(T&&) const&;
    T operator+(T&&) && ;

    friend T;
};

template<class T>
inline T Sumable<T>::operator+(const T & rhs) const&
{
    T result(*static_cast<const T*>(this));
    return std::move(result += rhs);
}

template<class T>
inline T Sumable<T>::operator+(const T & rhs) &&
{
    return std::move<T&>(*static_cast<T*>(this) += rhs);
}

template<class T>
inline T Sumable<T>::operator+(T && rhs) const&
{
    return std::move(rhs += *this);
}

template<class T>
inline T Sumable<T>::operator+(T && rhs) &&
{
    return std::move(rhs += *this);
}

Классы, которые поддерживают дополнение будет распространяться от Sumable<T> например class Vector : public Sumable<Vector>

Оператор перегружен в четыре раза, чтобы не допустить создания излишних временных объектов. Идея описана здесь https://stackoverflow.com/questions/6006527/overloading-on-r-value-references-and-code-duplicationно я вернусь т по значению, чтобы предотвратить проблемы, описанной в этом вопросе. Я тоже следую этой идее https://stackoverflow.com/questions/11224838/prevent-user-from-deriving-from-incorrect-crtp-base/11241079 чтобы предотвратить неправильное использование класса.

В простой тест, это сработало, как ожидалось. Есть какие-то крайние случаи, в которых эта конструкция может вызвать проблемы? И это хорошая идея, чтобы использовать эту конструкцию в производство код?

Редактировать Код, который я использовал, чтобы проверить это, компилируется под МСВС 2017:

#include "stdafx.h"
#include <iostream>
#include "Sumable.h"

using namespace std;

struct Test : public Sumable<Test>{
    int data;

    Test(int data) : data(data) {};
    Test(const Test& that) : data(that.data) 
    {
        cout << "copy" << endl;
    };
    Test(Test&& that) : data(that.data) 
    {
        cout << "move" << endl;
    };
    Test& operator+=(const Test& that) {
        this->data += that.data;
        return *this;
    }
};
int main()
{
    Test a{ 1 };
    Test b{ 2 };
    Test c{ 3 };
    cout << (a + b + c + a + b + c).data << endl;
    return 0;
}


Комментарии
1 ответ

Отсутствует заголовок

Мне пришлось #include <utility> чтобы получить определение std::move().

Не удаляйте свои деструкторы

Вы не покажите нам ваши тесты, но я боролся, чтобы получить мой очевидный код для компиляции. Часть причины была удалена конструкторы; я изменил по умолчанию конструктор protected и сделал копировать/переместить конструкторы protected и = defaultи тогда я смог наследовать без того, чтобы прямо написать, копировать/переместить строительство/назначить в моем подкласс:

struct Test : public Sumable<Test>
{
int i;
Test(int i) : i(i) {}
Test& operator+=(const Test& t) { i += t.i; return *this; }
};

Нет необходимости move() при возвращении

Я знаю, что вопрос с тегами C++11, но даже там, никакой пользы сомнительна (и, в частности, вряд ли могут существовать, когда функция включена). Когда вы переместите код к более новым стандартом, он активно вредны.

Передаче по значению, а не копировать с ссылкой

Если вы можете настаивать на обычные x + y == y + x (который, кажется, предположить, уже дали третий и четвертый методы, которые вычисляют rhs += *this), то вы можете пройти rhs по значению (так как нам нужно скопировать один из аргументов), что будет двигаться-построены, как и когда это уместно:

template<class T>
inline T Sumable<T>::operator+(T rhs) const
{
return rhs += *static_cast<const T*>(this);
}

Вот сейчас один метод вместо четырех.

Н. Б. Мне пришлось ввести static_cast там, как ваш код не компилируется с GCC 8.0 без него.

Рассмотрим свободные функции

Наследование не обязательно лучшее решение все в C++. Вам может быть лучше писать свободные функции вместо (в том же пространстве имен, классов, или в определенной области интеграции пространства имен, вдоль линий std::rel_ops).

Вот это начало, в том числе базовые испытания:

#include <type_traits>

namespace arithmetic_ops
{
template<typename T, typename U>
auto operator+(T t, U&& u)
-> typename std::remove_reference<decltype(t+=u)>::type
{
return t += u;
}

template<typename T, typename U>
auto operator+(T&& t, U u)
-> typename std::enable_if<!std::is_assignable<T,U>::value,
decltype(operator+(u,t))>::type
{
return u + t;
}
}

namespace test {
struct Test
{
int i;
Test(int i) : i(i) {}
Test& operator+=(const Test& t) { i += t.i; return *this; }
};
}

int main()
{
using namespace arithmetic_ops;

test::Test a(4);
test::Test b(-4);
return (0 + a + b + 0).i;
}

Я использовал переадресацию ссылки выше не копируется аргумент - то, что позволяет нам иметь только один метод для класса-первый случай (второй версии переопределение для таких случаев, как 0 + a где первый аргумент должен быть преобразован).

В enable_if во избежание двусмысленности, когда t + u и u + t обе возможности.

В ::type и ::value члены укоротиться из C++14, С _t и _v суффиксы к именам (напр. std::remove_reference_t<decltype(t+=u)> и std::enable_if_t<!std::is_assignable_v<T,U>, decltype(operator+(u,t))>.

a и/или b можно сделать const в этом тесте, не нарушая его.

4
ответ дан 2 апреля 2018 в 10:04 Источник Поделиться