Konstanter Pointer als Template-Argument

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Konstanter Pointer als Template-Argument

Beitrag von DerAlbi »

Hallo Leute...
der GCC mobbt mich.

Code: Alles auswählen

template<const GPIO_TypeDef* GPIO, const unsigned int Pin> struct CSpecificGPIO
{
...
};
Instantiiert über

Code: Alles auswählen

CSpecificGPIO<GPIOA, 1> Pin;
wobei GPIO eine fest definierte (hardgecodete) Adresse ist.

Code: Alles auswählen

#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
GCC hat geschrieben:'1073872896u' is not a valid template argument for 'const GPIO_TypeDef*' because it is not the address of a variable
Doch. Einer Variable mit fest definierter Position. Wasn hier das Problem? Wie lößt man das?
Ich hätte gern, dass es halbwegs Typsicher ist. Also dass man das Template nicht mit jedem beliebigen Pointer auf xyz füttern darf.

Mein derzeitiges Workaround...

Code: Alles auswählen

enum eGPIO {GPIO_A = GPIOA_BASE, GPIO_B = GPIOB_BASE, GPIO_C = GPIOC_BASE, GPIO_D = GPIOD_BASE,
			GPIO_E = GPIOE_BASE, GPIO_F = GPIOF_BASE, GPIO_G = GPIOG_BASE, GPIO_H = GPIOH_BASE };  

template<const eGPIO gpio, const unsigned int Pin> struct CSpecificGPIO
{
	static constexpr GPIO_TypeDef* GPIO = (GPIO_TypeDef*)gpio;
        .....
Aber das kanns ja wohl nicht sein :-/
Zuletzt geändert von DerAlbi am 30.07.2016, 14:46, insgesamt 1-mal geändert.
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Konstanter Pointer als Template-Argument

Beitrag von Spiele Programmierer »

Wie ist den "GPIOA_BASE" und so weiter definiert? Du machst doch hier einen reinterpret_cast zur Compile Time. Das verbietet der C++ Standard. Einzige Lösung ist es, dass "GPIOA_BASE" mit dem ursprünglichen Pointertyp zu übergeben und dann zur Laufzeit zu casten. Das ist übrigens bis auf ein paar Ausnahmen Undefined Behaviour. Das Prinzip nennt sich Strict Aliasing. Bei GCC und Clang gibt es eine Erweiterung "__attribute__((may_alias))" mit dem du den Typen in den du castest markieren kannst, um das zu umgehen. Aber zur Compile Time kannst du das nicht machen.

(Nebenbei angemerkt halte ich es für eine nicht so gute Idee, Dinge die nicht Präprozessordefines sind als Großbuchstaben zu deklarieren)
Benutzeravatar
Krishty
Establishment
Beiträge: 8237
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Konstanter Pointer als Template-Argument

Beitrag von Krishty »

Ich gehe davon aus, dass das der Basiszeiger irgendeines Segments ist, oder? Ich würde komplett auf Templates verzichten, es normal als Parameter übergeben, globale Optimierungen einschalten, und mich darauf verlassen, dass GCC den Wert von allein ersetzt wenn es sieht, dass die Funktion immer damit aufgerufen wird.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: Konstanter Pointer als Template-Argument

Beitrag von DerAlbi »

Also im obigen Beispiel ist GPIOA_BASE direkt als eine Integer-Konstante definiert.. also irgendwas mit 0x... naja so, dass 1073872896u rauskommt. Die definition sind mir über einen C-Header so vorgegeben (vom hersteller des Prozessors)
Das Ganze ist/wird ein Hardware-Abstraktions-Layer.. ich würde da gern so gut es geht auf Objektorientierung setzen. Das Template macht das halt extrem bequem. So kann ich direkt bei der Definition (hier eines Pins) beschrieben, wo er liegt und muss mich um die Kleinigkeiten beim Benutzen des Pins nicht mehr drum kümmern, wo und wie der irgendwo Angesprochen wird. Mit anderen Worten: die Compile-Time-Konstanten, die man sonst jeeedes mal den Funktionen übergeben würde (wie Kishty vorschlägt), möchte ich per Template direkt und auch nur _ein mal_ in die Deklaration schreiben und gut.
Benutzeravatar
Krishty
Establishment
Beiträge: 8237
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Konstanter Pointer als Template-Argument

Beitrag von Krishty »

Dann mach den Template-Parameter size_t, übergib die Konstante, und caste innerhalb des Templates :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: Konstanter Pointer als Template-Argument

Beitrag von DerAlbi »

Dann akteptiert es aber jeden scheiß als Input :-) Sind C++-Mechanismen nicht dafür da, sowas zu verhindern?
Ich meine.. letztlich ist die enum-Methode da wohl am Typsichersten, oder? hmmh.
Benutzeravatar
Krishty
Establishment
Beiträge: 8237
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Konstanter Pointer als Template-Argument

Beitrag von Krishty »

Man könnte entgegnen, C++-Mechanismen sind aber nicht für festgeschriebene Speicheradressen da, sondern für eine relativ abstrakte Maschine ohne Einschränkungen im Adressraum ;)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Konstanter Pointer als Template-Argument

Beitrag von Spiele Programmierer »

Ich kann dir versichern, dass es keinen Standard C++ Mechanismus gibt, um das zu erreichen.
Ich würde auch empfehlen einfach ein uintptr_t zu übergeben. (Streng genommen passender als ein size_t oder täusche ich mich da?)
Falls das so eine Art Library werden soll, ist deine Enum-Idee gar nicht so schlecht. Mit einem C++11 Enum (enum class eGPIO : uintptr_t { ... }) ist es jedenfalls auch recht sicher zu verwenden. #define würde ich vermeiden wenn es so einfach geht wie hier.

Deine constexpr-Variable mit dem reinterpret_cast dürfte eigentlich nicht kompilieren.
Clang 3.8 akzeptiert das jedenfalls auch nicht. GCC kompiliert es aus irgendeinen Grund.
http://gcc.godbolt.org/#compilers:!((co ... ,version:3
Benutzeravatar
Krishty
Establishment
Beiträge: 8237
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Konstanter Pointer als Template-Argument

Beitrag von Krishty »

Spiele Programmierer hat geschrieben:Ich würde auch empfehlen einfach ein uintptr_t zu übergeben. (Streng genommen passender als ein size_t oder täusche ich mich da?)
Nicht streng genommen passender, sondern absolut passender :) Hatte nur den Namen vergessen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: Konstanter Pointer als Template-Argument

Beitrag von DerAlbi »

Huui also eure Antworten gefallen mir ja mal üüüüüberhaupt nicht :-D
Ne, also im Ernst.. das uintptr_t ist bei mir auch nur ein Typedef zu unisnged int. In dem Moment finde ich die Lösung über diesen Enum-Mehraufwand tatsächlich noch am hübschesten. Da bekomm ich zumindest ne Fehlermeldung, wenn ich da die falsche Peripherie angebe und die Code-Vervollständigung macht mir beim Schreiben des Templates beim anlegen einer Variablen auch sinnvolle Vorschläge.
Ich bin leider auf die #defines angewiesen, wenn der code von CPU zu CPU portabel sein soll...
Naja... danke für eure Stellungnahme :-) Ich finds interessant, dass es eigentlich gar nicht gehen dürfte... ich finds noch viel interessanter, dass die constexpr in sub-klassen (verschachtelten Klassen) sichtbar ist. ich dachte, das wäre nicht so. Aber das ist hier auch mein erster Kontakt mit C++0xXX.
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Konstanter Pointer als Template-Argument

Beitrag von Spiele Programmierer »

Bei uintptr_t geht es um zwei Dinge:
  1. Verständlicherer Code. "unsigned int" ist irgendeine komische Größe von der keiner so recht weiß was sie darstellen soll. "uintptr_t" sagt ganz klar: "Es ist ein Integer der einen Pointer darstellt". Steht sogar im Namen.
  2. Portablerer Code. Der Typedef ist ja nach System durchaus unterschiedlich. Du redest von kompatiblität von "CPU zu CPU". uintptr_t wird zB. auf 64 Bit Systemen 64 Bit groß und passt sich auf exotischeren Architekturen auch entsprechend an.
Benutzeravatar
xq
Establishment
Beiträge: 1581
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: Konstanter Pointer als Template-Argument

Beitrag von xq »

ich hatte mal was ähnliches auf einem AVR, habe mich auf compiletime-optimization verlassen und sowas gebaut:

Code: Alles auswählen

enum class PortName { A, B };
template<PortName p>
struct Port
{
uint8_t read() {
switch(p) {
case PortName::A; return PORTA;
case PortName::B; return PORTB;
}
}
};
Das wurde nacher super wegoptimiert und es stand noch die eine einzelne passende assembler-instruktion da. "absolut" typesafe und trotzdem sehr flott.
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: Konstanter Pointer als Template-Argument

Beitrag von DerAlbi »

So habe ich das jetzt auch gelößt.. also mit Enum. Allerdings anstatt dem Switch-Case nutzte ich eine static constexpr, damit ich quasi alles direkt Adressieren kann.
Mein Punkt war, dass ich drum rum kommen wollte, trotz einer gewissen Typsicherheit alle Enums nochmal selbst schreiben zu müssen.

das sieht dann jetzt halt so aus:

Code: Alles auswählen

#define IO_READ(x)  ( static_cast<decltype(x)>( *const_cast< typename std::remove_volatile<decltype(x)>::type* >(&(x))  ))

enum ePinSpeed { psUltraLow = 0, psLow = 1, psMedium = 2, psHigh = 3 };
enum eInputPullDirection { ipdHighZ = 0, ipdPullUp = 1, ipdPullDown = 2 };
enum eGPIO {GPIO_A = GPIOA_BASE, GPIO_B = GPIOB_BASE, GPIO_C = GPIOC_BASE, GPIO_D = GPIOD_BASE,
            GPIO_E = GPIOE_BASE, GPIO_F = GPIOF_BASE, GPIO_G = GPIOG_BASE, GPIO_H = GPIOH_BASE };  //because C++ sucks

template<const eGPIO gpio, const unsigned int Pin> struct CSpecificGPIO
{
    static constexpr GPIO_TypeDef* GPIO = (GPIO_TypeDef*)gpio;
    struct COutput
    {
        void High() const { GPIO->BSRRH = 1u<<Pin; }
        void Low() const {  GPIO->BSRRL = 1u<<Pin; }
        void SetLevel(const bool Level) const { if (Level) High(); else Low(); }
        void Toggle() const { GPIO->ODR = IO_READ(GPIO->ODR) ^ (1u<<Pin); }
        void SetDriveStrength(const ePinSpeed PinSPeed) const { GPIO->OSPEEDR = (IO_READ(GPIO->OSPEEDR) & (~(3u<<(Pin*2)))) | (((unsigned int)PinSPeed)<<(Pin*2));  }
        void operator()() const
        {
            GPIO->PUPDR = (IO_READ(GPIO->PUPDR) & (~(3u<<(Pin*2)))) | (0u<<(Pin*2));    //No Pull up/down
            GPIO->OTYPER = IO_READ(GPIO->OTYPER) & ~(1u<<Pin);                          // PushPull
            GPIO->MODER = (IO_READ(GPIO->MODER) & (~(3u<<(Pin*2)))) | (1u<<(Pin*2));    // Output
            GPIO->OSPEEDR = IO_READ(GPIO->OSPEEDR) | (((unsigned int)psHigh)<<(Pin*2)); //HighSpeed as default
        }
        void operator()(const bool InitialLevel, const ePinSpeed PinSpeed = psHigh) const
        {
            GPIO->PUPDR =  (IO_READ(GPIO->PUPDR)  & (~(3u<<(Pin*2)))) | (0u<<(Pin*2));  // No Pull up/down
            GPIO->OTYPER = IO_READ(GPIO->OTYPER) | (1u<<Pin);                           // PushPull
            SetDriveStrength(PinSpeed);                                                 // Pin Speed
            GPIO->MODER = (IO_READ(GPIO->MODER) & (~(3u<<(Pin*2)))) | (1u<<(Pin*2));    // Output
            if (InitialLevel) High(); else Low();                                       // Level
        }
    } Output;
....
};
Nicht sonderlich schlimm.. aber auch nicht so, wie ich es mag. Auf einem anderen Prozessor mit mehr Ports muss ich dann wieder die Enums nachbearbeiten und für einen kleineren Prozessor müsste ich mt dem #Präprozessor die ungültigen enums wegmachen und argh. Wird halt hässlich.
Und wieso geht die Texteinrückung im Code-Tag nicht. Ich habs mit Tabs probiert und auch die Tabs durch Leerzeichen ersetzt. Aha. code=cpp
Antworten