Как написать наследуемый шаблон класса с операторами - PullRequest
3 голосов
/ 06 июня 2019

Я надеюсь, что можно написать шаблонный класс, который будет унаследован для нескольких подклассов, зависящих от типа. Я хочу, чтобы унаследованные методы и операторы возвращали тип подкласса, а не родительский тип шаблона. Это в надежде сэкономить много усилий на разработку и сопровождение, если мне придется изменить только один базовый класс.

Вот пример того, что у меня уже есть:

template<typename T> struct TMonoPixel
{
    T value;

    TMonoPixel(T v) { value = v; }

    // the template has some pure virtual functions here...

    TMonoPixel operator+ (const TMonoPixel& other)
    { return TMonoPixel(value + other.value); }
}

struct Mono8Pixel : TMonoPixel<uint8_t>
{
    using TMonoPixel::TMonoPixel;    // I want to inherit the constructor
    // each pixel type implements the virtual functions in the template
}

Как видите, структура Mono8Pixel наследует оператор +, который принимает TMonoPixel, но при использовании этого оператора возвращается TMonoPixel<uint8_t> вместо Mono8Pixel, поскольку он определен в базовом классе.

Я планирую использовать эти структуры для перебора пикселей на изображении:

Image* img; // img has an unsigned char* pointer to its pixel data
for (int row=0; row<img->height; row++) {
    for (int col=0; col<img->width; col++) {
        int i = (row*img->width + col);
        Mono8Pixel* pixel = reinterpret_cast<Mono8Pixel*>(img->dataPtr + sizeof(unsigned char)*i);
        // modify the pixel ...
    }
}

Есть ли способ изменить только класс шаблона, чтобы Mono8Pixel(2) + Mono8Pixel(2) возвращал Mono8Pixel?

Обратите внимание, что каким бы ни было решение, эти структуры должны поддерживать стандартную компоновку из-за того, как я хочу их использовать.

1 Ответ

1 голос
/ 06 июня 2019

То, что вы хотите, может быть сделано с помощью любопытно повторяющегося шаблона (CRTP). Основная идея такова:

template<class Pixel> struct TMonoPixel {
    ...

    // not virtual
    std::string GetSomeProperty() const {
        return static_cast<const Pixel&>(*this).GetSomeProperty();
    }

    Pixel operator+(const TMonoPixel& other) const {
        return Pixel(value + other.value);
    }
};

struct Mono8Pixel : TMonoPixel<Mono8Pixel> {
    using TMonoPixel::TMonoPixel;

    std::string GetSomeProperty() const {
        return "My name is Mono8Pixel";
    }
};

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

template<class T>
void foo(const TMonoPixel<T>& number) {
    std::cout << number.GetSomeProperty();    
}

Mono8Pixel i;
foo(i);

Обратите внимание, что внутри TMonoPixel, Pixel является неполным типом, поэтому у вас есть некоторые ограничения на то, как его можно использовать. Например, вы не можете сделать это:

template<class Pixel> struct TMonoPixel {
    Pixel::Type operator+(const TMonoPixel& other);
};

struct Mono8Pixel : TMonoPixel<Mono8Pixel> {
    using Type = std::uint8_t;
};

Типовые черты полезны для преодоления таких ограничений:

struct Mono8Pixel;

template<class Pixel> struct ValueType;

template<> struct ValueType<Mono8Pixel> {
    using Type = std::uint8_t;
};

template<class Pixel> struct TMonoPixel {
    using Type = typename ValueType<Pixel>::Type;
    Type value;

    TMonoPixel(Type value) : value(value)
    {}

    Pixel operator+(const TMonoPixel& other) const {
        return Pixel(value + other.value);
    }
};

struct Mono8Pixel : TMonoPixel<Mono8Pixel> {
    using TMonoPixel::TMonoPixel;
};

Тип Mono8Pixel(2) + Mono8Pixel(2) является Mono8Pixel.

Итак, я предполагаю, что спрашиваю, имеют ли эти структуры на основе CRTP стандартную компоновку после всех этих изменений типа value.

Они делают:

static_assert(std::is_standard_layout_v<Mono8Pixel>);

Полный пример: https://godbolt.org/z/8z0CKX

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...