Vuwox
Vuwox

Reputation: 2359

How to have inheritance between template with union?

I have the following two objects. Im wondering if there is a way to have Pixel as a base class of PixelBGR so that any operator (+,-,*,/, [], etc.) could be used without redefining them ?

template<class T, std::size_t N>
struct Pixel
{
    T ch[N];

    inline T& operator[](const int x)
    {
        return ch[x];
    }
};


template<class T>
struct PixelBGR
{
    union
    {
        struct
        {
            T b;
            T g;
            T r;
        };
        T ch[3];
    };

    inline T& operator[](const int x)
    {
        return ch[x];
    }
};

EDIT: As suggested by πάντα ῥεῖ, here more details about what Im trying to do.

Im trying to have a generic class Pixel, which will be template to handle any type or size.

The usual are 1,2,3,4,8 or 16. The class with defines some operator such as +,-,*, etc.

Since most of the time, the Pixel<T,3> is a BGR pixel, I would like to define rapid access to r,g and b to avoid confusion, but still store it as BGR.

But the derived class should also provide the Operator which will be generic based on N.

EDIT2: By reading the comment of SergeyA, I forgot to say that the struct Pixel must not change size.

So I think balki answer is the best, by using member function. I was trying to make it with variables to avoid too much char ie: adding the (), but it seems to be too complicated for nothing. I still investigating CRTP, but I dont get it well, Im reading on that.

Upvotes: 1

Views: 393

Answers (3)

Vuwox
Vuwox

Reputation: 2359

First thanks to all of you for advise, and special thanks to @Constantinos Glynos, @balki and @SergeyA for the example they provide, those help me to achieve a solution that match my need.

I implemented the BGR and BGRA to show that the N works fine, now I just need to implement all the operator, and functions that I require.

Please feel free to edit, or tell me if there is something wrong with this.

Pixel.h

#include <cstdio>   // std::size_t
#include <iostream>     // std::cout


template<typename T, std::size_t N, template<typename, std::size_t> class B >
struct Pixel
{
    T ch[N];

    // ==============================================================
    // Overload the accessor (so .ch[0] == direct access with [0].
    T& operator[](std::size_t x){ return ch[x]; }

    // ==============================================================
    // Copy-assignement
    Pixel& operator=( const Pixel &t )
    {
        for ( int i = 0; i < N; i++ )
            ch[i] = t.ch[i];
        return *this;
    }

    // ==============================================================
    // Operator
    B<T, N> operator+( const B<T, N> &t )
    {
        B<T, N> tmp;
        for ( int i = 0; i < N; i++ )
            tmp[i] = ch[i] + t.ch[i];
        return tmp;
    }

    B<T, N> operator-( const B<T, N> &t )
    {
        B<T, N> tmp;
        for ( int i = 0; i < N; i++ )
            tmp[i] = ch[i] - t.ch[i];
        return tmp;
    }

    template<typename T, std::size_t N, template<typename, std::size_t> class B >
    friend std::ostream& operator<<( std::ostream& os, const Pixel &t );
};

// To print the vector
template<typename T, std::size_t N, template<typename, std::size_t> class B >
std::ostream& operator<<( std::ostream& os, const B<T, N> &t )
{
    os << "Pixel: (" << t.ch[0];
    for ( int i = 1; i < N; i++ )
        os << ", " << t.ch[i];
    os << ")";
    return os;
}




template<typename T, std::size_t N = 3>
struct BGR : Pixel<T, N, BGR>
{
    T& b() { return ch[0]; }
    T& g() { return ch[1]; }
    T& r() { return ch[2]; }
};


template<typename T, std::size_t N = 4>
struct BGRA : Pixel<T, N, BGRA>
{
    T& b() { return ch[0]; }
    T& g() { return ch[1]; }
    T& r() { return ch[2]; }
    T& a() { return ch[3]; }
};

Main.cpp

int main() {
    std::cout << "Sizeof a float BGR: " << sizeof(BGR<float>) << std::endl;
    std::cout << "Sizeof a float BGRA: " << sizeof(BGRA<float>) << std::endl;

    BGR<int> p;
    p.r() = 25;
    p.g() = 14;
    p.b() = 58;

    std::cout << p << std::endl;
    std::cout << p[0] << " , " << p[1] << " , " << p[2] << std::endl;
    std::cout << p.b() << " , " << p.g() << " , " << p.r() << std::endl;

    BGR<int> q;
    q = p;
    std::cout << q[0] << " , " << q[1] << " , " << q[2] << std::endl;

    BGR<int> res1;
    res1 = q + p;
    std::cout << res1.r() << " , " << res1.g() << " , " << res1.b() << std::endl;

    BGR<int> res2;
    res2 = q - p;
    std::cout << res2.r() << " , " << res2.g() << " , " << res2.b() << std::endl;

    BGRA<float> a;
    a.r() = 255.0f;
    a.g() = 0.0f;
    a.b() = 0.0f;
    a.a() = 128.5f;

    BGRA<float> b = a;
    std::cout << a << std::endl;

    return 0;
}

Upvotes: 0

Constantinos Glynos
Constantinos Glynos

Reputation: 3186

The Curiously Recurring Template Pattern (CRTP) would work well in this case. In the CRTP the Derived class is used as a template argument to the Base class. Chapter 16.3 The Curiously Recurring Template Pattern (CRTP), from the C++ Templates - The Complete Guide, by David Vandevoorde and Nicolai M. Josuttis, explains things in more detail.

From the comments below, the usage of a union{struct{...}...} causes undefined behaviour (UB), but there have been some contradicting opinions upon this. As far as I'm aware, it is a gnu extension and supported by almost every compiler. glm for example uses union-structs quite very often.

As an alternative approach, you can use aliases (references) for the r,g,b variables.

#include <iostream>

template<typename T, std::size_t N, template<typename,std::size_t> class B >
struct Pixel
{
    B<T,N> *crtp = static_cast<B<T,N>*>(this);

    T& operator[](std::size_t x)
    {
        return crtp->ch[x];
    }

    Pixel& operator = (const Pixel &t)
    {
        crtp->ch[0] = t.crtp->ch[0];
        crtp->ch[1] = t.crtp->ch[1];
        crtp->ch[2] = t.crtp->ch[2];
        return *crtp;
    }

    B<T,N> operator + (const B<T,N> &t)
    {
        B<T,N> tmp;
        tmp[0] = crtp->ch[0] + t.crtp->ch[0];
        tmp[1] = crtp->ch[1] + t.crtp->ch[1];
        tmp[2] = crtp->ch[2] + t.crtp->ch[2];
        return tmp;
    }

    B<T,N> operator - (const B<T,N> &t)
    {
        B<T,N> tmp;
        tmp[0] = crtp->ch[0] - t.crtp->ch[0];
        tmp[1] = crtp->ch[1] - t.crtp->ch[1];
        tmp[2] = crtp->ch[2] - t.crtp->ch[2];
        return tmp;
    }
};

template<typename T, std::size_t N=3>
struct PixelBGR : Pixel<T, N, PixelBGR>
{
    T ch[3];
    T &r;
    T &g;
    T &b;

    PixelBGR() : ch{},r(ch[0]),g(ch[1]),b(ch[2])
    {}
    PixelBGR& operator = (const PixelBGR &p)
    {
        ch[0] = p.ch[0];
        ch[1] = p.ch[1];
        ch[2] = p.ch[2];
        return *this;
    }
};

int main()
{
    PixelBGR<int> p;

    p.r = 25;
    p.g = 14;
    p.b = 58;

    std::cout<< p[0] <<" , "<<p[1]<<" , "<<p[2] <<std::endl;

    PixelBGR<int> q;
    q = p;
    std::cout<< q[0] <<" , "<<q[1]<<" , "<<q[2] <<std::endl;

    PixelBGR<int> res1;
    res1 = q + p;
    std::cout<< res1.r <<" , "<<res1.g<<" , "<<res1.b <<std::endl;

    PixelBGR<int> res2;
    res2 = q - p;
    std::cout<< res2.r <<" , "<<res2.g<<" , "<<res2.b <<std::endl;
}

Result:

25 , 14 , 58
25 , 14 , 58
50 , 28 , 116
0 , 0 , 0

Example using references: https://rextester.com/AZWG4319

Example using union-struct: https://rextester.com/EACC87146

Upvotes: 1

SergeyA
SergeyA

Reputation: 62563

Answering the question as asked, this should give OP reuse of the operators without any undefined behavior:

#include <cstddef>

template<class T, std::size_t N>
struct Pixel
{
    T ch[N];

    inline T& operator[](const int x)
    {
        return ch[x];
    }
    Pixel& operator+= (const Pixel& ) { return *this;}

};

template<class T, std::size_t N>
Pixel<T, N> operator+ (const Pixel<T, N>& l, const Pixel<T, N>& r);

template<class T>
struct BgrPixel : Pixel<T, 3> {
    using base = Pixel<T, 3>;
    using base::base;
    BgrPixel(const base& b) : base(b) { };
    T& b = base::ch[0];
    T& g = base::ch[1];
    T& r = base::ch[2];
};


BgrPixel<int> a, b;

BgrPixel<int> c = a + b;

Alterinative would be to have b(), g() and r() as a member functions, but this would require you to access them as functions. You would also need const and non-const versions of them.

The benefits, however, would be that the size of the struct will not be increased and copy assignment would work naturally (which, in turn, could be solved by providing custom copy assignment).

Upvotes: 1

Related Questions