Toon posts:

[c++] Template method probleem.

Pagina: 1
Acties:

Verwijderd

Topicstarter
Ik probeer een generieke image library te schrijven waar ik een beetje hobbymatig mee kan rotzooien, maar loop tegen wat merkwaardige problemen aan.

Image class met pixel policy.
Mijn eigen image class is niet afhankelijk van een bepaald pixel type, ik gebruik een template variable om mijn image structuur te laten weten welk pixel type er gebruikt moet worden. Zie de image als een N-dimensionale container die een willekeurig type pixel opslaat. Bijvoorbeeld grayscale, (A)RGB, CMYK of wat dan ook. Het aantal dimensies van de image is ook variabel, maar dat laat ik even buiten beschouwing.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
// image class, simplified:
template <typename P>
struct image
{
    P* data;  // the buffer that stores pixel data
    image(int w, int h) {...} // constructor
};
// 4-channel ARGB pixel:
struct argb_32b
{
    unsigned char c[4];
};
image<argb_32b>* img = new image<argb_32b>(320, 200);

Casting de pixel buffer naar een ander type.
Omdat het voor image operaties niet altijd gemakkelijk is om te werken met multi channeled pixels moet er soms gecast worden. Dit kan natuurlijk ook mooi middels templates waardoor dit netjes achter de schermen gebeurt, bijvoorbeeld in de desbetreffende image operatie.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template <typename P, typename T>
void solid_fill(image<P>* img, T p)
{
        // decides compile whether to throw runtime
        if (sizeof(P) != sizeof(T))
                throw std::bad_cast();
        T* data = reinterpret_cast<T*>(img->data);

        // for all x and all y {
            data[y * img->width() + x] = p;
        // }
}
// now both ways possible:
solid_fill(img, 0x00FF0000);
argb_32b p(0, 0xFF, 0, 0);
solid_fill(img, p);

Veilig en simpele casting method van image.
Het komt met image operaties nu eenmaal vaak voor dat er gecast moet worden. Een solid_fill methode will niets weten van losse kanalen, maar meer ingewikkelde algoritmen willen deze controle misschien wel hebben. Wat ik heb gedaan is deze safe cast een methode van image laten zijn. Het is een methode die deze cast makkelijker en veiliger maakt. Een cast naar een type met ander formaat wordt afgevangen. Dit zou namelijk desastreus zijn.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// image class, simplified:
template <typename P>
struct image
{
    P* data;  // the buffer that stores pixel data

    template <typename T>
    inline T* cast() const throw()
    {
        if (sizeof(T) << 3 != P::bpp)
           throw std::bad_cast();
        return reinterpret_cast<T*>(data);
    }
};

Het eigenlijk probleem.
Na deze nogal grote en wellicht irrelevante inleiding komt nu eindelijk het probleem. Indien ik deze cast() methode gebruik in de main functie (main(int argc, char* argv[])) werkt deze prima en doet het zijn werk zonder probleem. Maar indien ik exact dezelfde code gebruik in een andere functie gaat de compiler (gcc version 4.0.1 / powerpc-apple-darwin8) keihard over z'n nek.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template <typename P, typename T>
void solid_fill(image<P>* img, T p)
{
    img->cast<T>()[0] = p;     // Both these lines fail with a compiler error.
    img->cast<int>()[0] = 10;

    // error: expected primary-expression before '<' token
    // error: expected primary-expression before 'int'
    // error: expected ',' or ';' before 'int'
}

int main (int argc, char *argv[])
{
    image_2d<argb_32b>* img = new image_2d<argb_32b>(640, 480);
    img->cast<int>()[0] = 0x00FF0000; // Compiles and works fine.
    solid_fill(img, 0x00FF0000);
    delete img;
}

Kan iemand mij uitleggen waarom dezelfde code zonder problemen functioneert in de main functie maar in een andere functie tot een error leidt? Ik heb zelf namelijk totaal geen idee.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 01:22

.oisyn

Moderator Devschuur®

Demotivational Speaker

image<P>::cast is een dependent name. Bij het parsen van de solid_fill functie weet de compiler nog niet met wat voor P hij gebruikte kan worden, en door specialization kan het voor een bepaalde P een functie zijn, terwijl het voor een andere P een type kan zijn. Ook kan hij niet weten of die functie of type een template is of niet. Daarom is het in C++ verplicht de keywords typename en template te gebruiken bij dependent names om aan te geven of het resp. een type is en of het een template is. De code wordt dus:

C++:
1
2
3
4
5
6
7
8
9
10
template <typename P, typename T> 
void solid_fill(image<P>* img, T p) 
{ 
    img->template cast<T>()[0] = p;     // Both these lines fail with a compiler error. 
    img->template cast<int>()[0] = 10; 

    // error: expected primary-expression before '<' token 
    // error: expected primary-expression before 'int' 
    // error: expected ',' or ';' before 'int' 
} 


Zie ook 14.6.2 - Dependent names

[ Voor 9% gewijzigd door .oisyn op 22-03-2006 16:27 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Die error is vanwege wat oisyn zegt, maar komt direct voort uit het feit dat de compiler "cast" opvat als non-template. Wat hier dus staat is "img->cast < ...", ofwel een boolean vergelijking tussen img->cast, en T, gevolgd door een vreemde ">". Resultaat is een parse error.

(twee dingen over je solid fill: ten eerste kan je iets gebruiken als boost::static_assert, of zelf even zo'n regel schrijven, om een compile time error te geven als sizeof(a) != sizeof(b). Dat is wat netter dan een error die je al gedetecteerd hebt tot runtime te laten wachten. Een voordeel van templates is juist die compile time error testing. En ten tweede, niet echt belangrijk, maar het is wellicht sneller om iets als memfill() te gebruiken dan handmatig die array van pixels door te lopen. memfill optimaliseert automatisch naar je platform address grootte)

[ Voor 55% gewijzigd door Zoijar op 22-03-2006 16:40 ]


Verwijderd

Topicstarter
Dank u. Dit werkt inderdaad wel. Jammer dat de syntax de look van mijn functies niet bijzonder veel schoner maakt. Maar na dat docje snap ik het punt in ieder geval.

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Je kan trouwens ook eens kijken hoe "vigra" dit doet. Dat is een template based image processing library in C++.

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 10-12-2025
Overigens: een reinterpret_cast<> gebruiken? Zeker vanwege de (aangenomen) snelheid. Maar wat doet een regel als
C++:
1
data[y * img->width() + x] = p;
dan daar? Die vind ik hoogst verdacht.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein

Pagina: 1