Forum: Mikrocontroller und Digitale Elektronik AVR GPIOR Bit Verwaltung C++


Beitrag #5249494 wurde von einem Moderator gelöscht.
Beitrag #5249510 wurde von einem Moderator gelöscht.
von Wilhelm M. (wimalopaan)


Lesenswert?

Johann L. schrieb:
> Ich versteh immer weniger.  Ein
>
1
template<> struct X<1>;
> ist doch eine explizite Instanziierung.  Das wirft
>
1
> In function 'int main()':
2
> error: incomplete type 'X<1>' used in nested name specifier
3
>    return X<1>::x;
4
>                 ^
>
> Wieso ist X<1> inclomplete? Irgendwie überschreibt das "template<>
> struct X<1>;" das Struct aus dem Template, und danach ist X<1>
> incomplete so wie wie Y in "struct Y;" inclomplete ist.

Ich vermute, Du hast jetzt folgendes:
1
template<int>
2
struct X {
3
    static constexpr int x = 0;
4
};
5
6
template<>
7
struct X<1>; // Deklaration der vollständigen Spezialisierung
8
9
int main() {   
10
    X<0> a; // Instanziierung,  parametriert mit 0 
11
    return X<1>::x; // Instanziierung,  parametriert mit 1
12
}

Hier hast Du das allg. Template definiert, das spezielle aber nur 
deklariert. Deswegen kannst Du das spezielle Template nicht 
instanziieren, denn
1
template<>
2
struct X<1>;

ist keine Template-Instanziierung, sondern die Deklaration einer 
Spezialisierung.

Fügst Du nun die Template-Definition ein (die Deklaration könntest Du 
nun weglassen), ist es ok:
1
template<int>
2
struct X {
3
    static constexpr int x = 0;
4
};
5
6
template<>
7
struct X<1>; // Deklaration der vollständigen Spezialisierung (hier nicht notwendig)
8
9
template<>
10
struct X<1> { // Template-Definition
11
    static constexpr int x = 1;
12
};
13
14
//template<>
15
//constexpr int X<1>::x;
16
17
int main() {   
18
    X<0> a; // Instanziierung,  parametriert mit 0 
19
    return X<1>::x; // Instanziierung,  parametriert mit 1
20
}

Die Instanziierungen erfolgt in main().

Beitrag #5249752 wurde von einem Moderator gelöscht.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

X<1> ist eine Klasse, und von dieser will man ein statisches Member 
verwenden, hier X<1>::x.  Das Layout von Objekten im Speicher darf nicht 
von deren Verwendung abhängen (modulo as-if Rule).  Für X<1>::x bedeutet 
dies, dass diese Komponente eine Speicherinstanz haben muss für den 
Fall, dass irgebdwo die Adresse von x genommen wird.  Das

template<> constexpr int X<1>::x;

soll die entsprechende Definition sein, wie sie für andere statische 
Member auch nötig ist.

Um dass zu errechen bzw. überhaupt machen zu dürfen, muss erst X<1> als 
explizite Spezialisierung vorhanden sein wie von Dir angemerkt; damit 
ist PR83484 ungültig weil es keine explizite Spezialisierung hat, und 
der korrekte Code (inclusive Addressnahme von x) ist:
1
template<int> struct X { static constexpr int x = 0; };
2
3
template<> struct X<1> { static constexpr int x = 0; };
4
5
// template<> constexpr int X<1>::x;
6
7
int main() 
8
{
9
  const int * volatile p = & X<1>::x;
10
  return X<1>::x;
11
}
1
In function 'int main()':
2
-.o: In function `main':
3
-.cpp:(.text.startup+0xa): undefined reference to `X<1>::x'
Der Linkerfehler ist ok und beruht darauf, dass X<1>::x nicht definiert 
wurde. Entfernen des Kommentars, so dass eine Instanz von X<1>::x 
existiert,  ergibt:
1
-:3:47: warning: too many template headers for 'X<1>::x' (should be 0)
2
 template<> struct X<1> { static constexpr int x = 0; };
3
                                               ^
4
-.cpp:3:47: note: members of an explicitly specialized class are defined without a template header

Oder ist der Linkerfehler wie in PR83484, nur diesmal mit der nötigen 
expliziten Spezialisierung, und die Version mit Kommentar (also ohne 
"template<> constexpr int X<1>::x;") ist die korrekte, denn die 
Definition von ::x erfolgt bereits mit der Deklatation?

Beitrag #5249801 wurde von einem Moderator gelöscht.
Beitrag #5249866 wurde von einem Moderator gelöscht.
Beitrag #5249907 wurde von einem Moderator gelöscht.
von Wilhelm M. (wimalopaan)


Lesenswert?

Johann L. schrieb:
> X<1> ist eine Klasse,

sagen wir, es ist ein konkreter Typ ...

> und von dieser will man ein statisches Member
> verwenden, hier X<1>::x.  Das Layout von Objekten im Speicher darf nicht
> von deren Verwendung abhängen (modulo as-if Rule).  Für X<1>::x bedeutet
> dies, dass diese Komponente eine Speicherinstanz haben muss für den
> Fall, dass irgebdwo die Adresse von x genommen wird.

Ja.

>  Das
>
> template<> constexpr int X<1>::x;
>
> soll die entsprechende Definition sein, wie sie für andere statische
> Member auch nötig ist.

Nein, es ist die vollständige Spezialisierung eines einzelnen(!) members 
von X, und damit ohne Initialisierer nur eine Deklaration (s.a. den 
textausschnitt den ich gepostet hatte oder den Link in cppreference).

> Um dass zu errechen bzw. überhaupt machen zu dürfen, muss erst X<1> als
> explizite Spezialisierung vorhanden sein wie von Dir angemerkt;

Nein, das hast Du falsch verstanden: man sollte auch einzelnen static 
member spezialisieren dürfen, und da hat der gcc den Bug. Das darf man 
allerdings nur, wenn noch keine Spezialisierung des gesamten Templates 
vorliegt.

Bin jetzt nich ganz sicher, auf was Du Dich genau beziehst, deswegen am 
besten immer vollständigen Code.

Ausserdem scheint es von der Version abzuhängen. Mit gcc-7.2.1. ist 
folgendes ok (und richtig), Dein g++ (Version?) scheint es nicht zu 
mögen:
1
template<int> // Allg. template: Template-Definition
2
struct X {
3
    static inline constexpr int x = 0;
4
};
5
6
template<>
7
struct X<1> { // Vollständige Spezialisierung: Template-Definition
8
    static inline constexpr int x = 1;
9
};
10
11
//template<>
12
//constexpr int X<1>::x; // überflüssig, da schon eine vollst. Spezialisierung deklariert wurde, warnung bei g++, error bei clang++
13
14
//template<>
15
//constexpr int X<1>::x{}; // unsinnig (ill-formed), da schon eine vollst. Spezialisierung als Definition vorliegt, dies wäre dann die zweite Definition. error bei g++, error bei clang++
16
17
int main() {   
18
    const int * volatile p = & X<1>::x; // ok: gcc-7.2.1/gcc-8; nok: clang++-5.0
19
    return *p;
20
}

Nun kommt hinzu, dass der clang auch m.E. einen Bug hat, der Linker 
liefert nämlich dann undefined-reference.

(Daran kann man evtl. ablesen, dass die Möglichkeit, einzelne(!) Member 
zu spezialisieren, kaum von Anwendern genutzt wird ...).

Beitrag #5250152 wurde von einem Moderator gelöscht.
Beitrag #5250218 wurde von einem Moderator gelöscht.
Beitrag #5250580 wurde von einem Moderator gelöscht.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> (Daran kann man evtl. ablesen, dass die Möglichkeit, einzelne(!) Member
> zu spezialisieren, kaum von Anwendern genutzt wird ...).

Das war garnicht das Ziel!  Sondern es ging darum, einen Linkerfehler zu 
beheben wie in
1
struct X { static const int x = 0; };
2
// const int X::x;
3
4
const int* px () { return & X::x; }
5
6
int main () { return X::x; }
Hier muss X::x definiert werden, und das geschieht per "const int 
X::x;".  Der Linkerfehler entsteht also dadurch, dass die 
auskommentierte Zeile fehlt.  Hinzufügen der Zeile ist die Lösung des 
Problems.

Allerdings war es keine normale Klasse, sondern ein (komplexeres) 
Template und mit constexpr anstatt const:
1
template<int> struct X { static const int x = 0; };
2
template<> const int X<1>::x;
3
4
const int* px () { return & X<1>::x; }
5
6
int main () { return X<1>::x; }
Mir war nicht klar, dass dadurch, dass die Klasse einen 
Template-Parameter erhält, Zeile 2 plötzlich keine Definition mehr ist, 
sondern ill-formed sofern ich die Kommentare zu PR83484 recht verstehe.

Die alles entstand in einem Kontext, wo ich der immer wieder — auch in 
diesem Thread — gegeben Empfehlung folgte, einen Wert, der zur 
Compile-Zeit bekannt ist, in einem constexpr oder als einen Typen 
darzustellen.

Als zur Compilezeit bekannten Wert wählte ich &PORTD — um näher am Thema 
des Threads zu bleiben hätte ich auch &GPIOR0 wählen können.

Diese zur Compile-Zeit bekannten Adressen sind grundlegende Basis jeder 
Implementierung, die sich mit SFRs befasst; zumindest in einem Kontext, 
in der "Standard"-Header wie avr/io.h diese Werte zur Verfügung stellen. 
Die avr-libc enthält ca. 48000 so zur Verfügung gestellter Adressen.  Es 
ist also nicht komplett abwegig, diese Resource nutzen zu wollen. 
Alternative wäre, diesen Zoo magischer Zahlen neu zu erfinden.

Dass &GPIOR0 zur Compile-Zeit bekannt ist, sieht man an folgendem Code:
1
#include <avr/io.h>
2
3
extern volatile uint8_t * const addr;
4
volatile uint8_t * const addr = &GPIOR0;
5
6
volatile uint8_t * f()
7
{
8
    if (__builtin_constant_p (&GPIOR0))
9
        __asm ("; yes");
10
    else
11
        __asm ("; no");
12
    __asm ("; addr = %0" :: "n" (&GPIOR0));
13
    return &GPIOR0;
14
}
Übersetzt für einen ATmega168 und unabhängig von Optimierung, erzeugt 
avr-gcc daraus (hier mit -O0):
1
.section  .rodata
2
addr:
3
  .word  62
4
5
_Z1fv:
6
  ...
7
 ;  foo.cpp:9:         __asm ("; yes");
8
  ; yes
9
 ;  foo.cpp:12:     __asm ("; addr = %0" :: "n" (&GPIOR0));
10
  ; addr = 62   ; 
11
 ;  foo.cpp:13:     return &GPIOR0;
12
  ldi r24,lo8(62)
13
  ldi r25,0
14
  ...
Der Code compiliert ohne Warnung, insbesondere gibt es keine Warnung 
gegen Constraint "n".  Der Init-Wert von addr ist zur Compilezeit 
bekannt — sowohl im Code als auch in Initialzer für Daten im Static 
Storage.  Der Code enthält KEIN "; no" sondern nur ein "; yes".

Dein Code war keine Hilfe, da er aus didaktischen Gründen avr/io.h 
meidet und magische Zahlen verwendet.

Im Code von mcucpp hab ich versucht, die entspechende Stelle zu finden. 
Es werden Makros oder Funktionen verwendet, ohne die entsprechenden 
Header zu includen, etc.  Es ist also nicht ganz einfach sich da zu 
orientieren.  Und das runterzuladen und zu debuggen wollte ich mir nicht 
antun.  Mit Code, den man nur durch Debuggen verstehen kann, ist m.E. 
etwas falsch gelaufen; auch wenn er funktioniert, ist es wohl keine gute 
Quelle, um Best Practices zu sehen.

Und schließlich geht es nur darum, eine Compile-Zeit Konstante in 
constexpr oder in einen Typen zu bekommen, und das Paradigma dazu sollte 
in wenigen Zeilen darstellbar sein.  Auf der Suche nach diesem Paradigma 
bin ich über den Linkerfehler gestolpert (und wollte ihn verstehen 
anstatt irgendwie zu umgehen).

So langsam schwant mir aber, dass C++ als Designziel hat, bestimmte 
Compile-Zeit Konstanten dediziert als solche zu verbieten, insbesondere 
zur Compile-Zeit bekannte Adressen.

Ich gehe mal davon aus, dass constexp User-defined Literals oder 
constexpr Überladung von Cast-Operator mich dem Ziel auch nicht näher 
bringt da unerreichbar...

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Johann L. schrieb:

> Diese zur Compile-Zeit bekannten Adressen sind grundlegende Basis jeder
> Implementierung, die sich mit SFRs befasst;

Da aber immer ein reinterpret_cast notwendig ist bzw. literale keine 
lvalues sind, kann man sie so nicht verwenden (s.a. ein paar Post weiter 
oben von mir).

> zumindest in einem Kontext,
> in der "Standard"-Header wie avr/io.h diese Werte zur Verfügung stellen.
> Die avr-libc enthält ca. 48000 so zur Verfügung gestellter Adressen.  Es
> ist also nicht komplett abwegig, diese Resource nutzen zu wollen.
> Alternative wäre, diesen Zoo magischer Zahlen neu zu erfinden.

Nein.

Warum ich die Präprozessor-Makros nicht verwende, habe ich oben bereits 
geschrieben.

Aber natürlich kann man die Info aus den AVR-Headern verwenden:

1) Man erzeugt sich mit etwas Scripting alternative Header, in denen die
Adressen der SFRs - wie es meiner Meinung nach am besten wäre - als 
uintptr_t dargestellt sind.

2) Man setzt sich etwas über die Macros hinweg (s.u.)

Die Wurzel des Übels liegt leider in der "falschen" Wahl der Macros für 
die SFR-Adresswerte. Allerdings kann man es nicht wirklich den Urhebern 
anlasten, denn sie hatten vermutlich nie eine C++-Lösung im Sinn ;-)

Hier ein Beispiel:
1
#include <avr/io.h>
2
3
// um den C-style cast auf volatile T* los zu werden
4
#undef _MMIO_BYTE
5
#define _MMIO_BYTE(x) (uintptr_t(x))
6
7
// convenience-function für cast
8
template<typename C>
9
typename C::value_type* getAddress() {
10
    return reinterpret_cast<typename C::value_type*>(C::address_value);
11
}
12
13
template<uintptr_t address>
14
struct Port {
15
    inline static constexpr uintptr_t address_value = address;
16
    typedef volatile uint8_t value_type;
17
    typedef Port type;
18
    
19
    template<auto Bit>
20
    static void set() {
21
        *getAddress<type>() |= (1 << Bit);
22
    }
23
};
24
25
using gp0 = Port<GPIOR0>;
26
27
int main() {
28
    gp0::set<1>();
29
}

So sollte alles so sein, wie Du möchtest. Eine analoge Variante verwende 
ich in dem Code, den ich zur Verfügung gestellt hatte, allerdings ohne 
weder 1) noch 2) zu verwenden. Das hat einfach historische Gründe ;-)

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Ich habe prinzipiell nichts gegen C++ und nutze es auch selbst. Aber 
diese Templates sind mir ein Buch mit 7 Siegeln (zu abstrakt, zu 
kryptische Syntax). Glücklicherweise ist man nicht gezwungen, sie zu 
benutzen.

Beitrag #5251205 wurde von einem Moderator gelöscht.
Beitrag #5251255 wurde von einem Moderator gelöscht.
von Arc N. (arc)


Lesenswert?

Jörn S. schrieb:
> Wo kann ich Informationen finden wie ich sowas mit Templates umsetzen
> kann? Gibt es schon  Open Source Libs die diese Konzepte verwenden? Ich
> finde es sehr interessant und würde solche Konstrukte gerne in meinen
> nächsten Projekten verwenden.

Ist zwar z.Z. auf div. ARMs ausgerichtet, dürfte aber ein paar Ideen 
liefern:
https://github.com/kvasir-io/Kvasir
https://github.com/kvasir-io/Kvasir/wiki
Beispiele z.B.:
https://github.com/kvasir-io/Kvasir/tree/master/Lib/Register
Auf der Blogseite des Hauptautors gibt's weitere Ideen
https://odinthenerd.blogspot.de/

von Hans-Georg L. (h-g-l)


Lesenswert?

Arc N. schrieb:
> Jörn S. schrieb:
>> Wo kann ich Informationen finden wie ich sowas mit Templates umsetzen
>> kann? Gibt es schon  Open Source Libs die diese Konzepte verwenden? Ich
>> finde es sehr interessant und würde solche Konstrukte gerne in meinen
>> nächsten Projekten verwenden.
>
> Ist zwar z.Z. auf div. ARMs ausgerichtet, dürfte aber ein paar Ideen
> liefern:
> https://github.com/kvasir-io/Kvasir
> https://github.com/kvasir-io/Kvasir/wiki
> Beispiele z.B.:
> https://github.com/kvasir-io/Kvasir/tree/master/Lib/Register
> Auf der Blogseite des Hauptautors gibt's weitere Ideen
> https://odinthenerd.blogspot.de/

Zum Einstieg aber wenig geeigent weil zwar erklärt wird warum "alte" 
Lösungen Probleme haben aber dann sofort ohne weitere Erklärungen "die" 
Lösung angeboten wird.

Beitrag #5251459 wurde von einem Moderator gelöscht.
Beitrag #5251550 wurde von einem Moderator gelöscht.
Beitrag #5251554 wurde von einem Moderator gelöscht.
Beitrag #5251561 wurde von einem Moderator gelöscht.
von Oliver J. (skriptkiddy)


Lesenswert?

[OT]

Es ist mir schon öfters untergekommen, dass Leute vehement gegen etwas 
sind, nur weil sie es nicht verstehen. Aber damit lässt sich denke ich 
noch gut leben. Was aber gar nicht geht ist, wenn dann alles schlecht 
geredet wird, was das Unbekannte betrifft. Das zeugt meiner Meinung nach 
von höchster Unprofessionalität.

Aber es geht noch schlimmer: Wenn es destruktiv wird auf unsachliche 
oder gar persönliche Weise (wahrscheinlich um zu provozieren).

Irgendwie führt das bei Grundsatzdiskussionen hier häufig zu letzterem, 
was dann eher wenig Spaß macht.

Aber ich finde es gut, dass hier durch unsere Admins so etwas schnellst 
möglich entfernt wird. Danke Euch dafür. Wobei es wiederum doch etwas 
traurig ist wie viel in solchen Threads wie diesem hier entfernt werden 
muss.

Grüße Oliver

Beitrag #5251709 wurde von einem Moderator gelöscht.
Beitrag #5251711 wurde von einem Moderator gelöscht.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Oliver J. schrieb:
> Aber ich finde es gut, dass hier durch unsere Admins so etwas schnellst
> möglich entfernt wird. Danke Euch dafür.

Auch von mir.

> Wobei es wiederum doch etwas traurig ist wie viel in solchen
> Threads wie diesem hier entfernt werden muss.

Nur ein Scheinriese, wie in Jim Knopf.

Es sind nicht viele Beiträge, die gelöscht werden, sondern der 
immergleiche "Beitrag", den ein einziger "User" immer wieder postet oder 
posten lässt.

Von daher wird lediglich immer und immer wieder ein einziger "Beitrag" 
entfernt.

Also kein Riese, sondern nur ein Zwerg.

von Jan K. (jan_k)


Lesenswert?

Ich lese seit einiger Zeit mit und finde es mega interessant, also immer 
weiter :)

von Chris D. (myfairtux) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Es sind nicht viele Beiträge, die gelöscht werden, sondern der
> immergleiche "Beitrag", den ein einziger "User" immer wieder postet oder
> posten lässt.
>
> Von daher wird lediglich immer und immer wieder ein einziger "Beitrag"
> entfernt.

Jepp. Das lässt sich übrigens hervorragend mit Skripten automatisieren. 
Mit entsprechenden Heuristiken sind auch Abweichungen davon problemlos 
handhabbar. Wofür hat man denn seine Dipl.-Arbeit über Text- und 
Mustererkennung geschrieben :-}

Wer sich übrigens an dem Hinweis auf gelöschte Beiträge stört, kann 
diese auch ausblenden. Im Firefox sollte das so gehen:

Beitrag "3) FF - Gelöschte Beiträge ausblenden"

von Hans-Georg L. (h-g-l)


Lesenswert?

Wilhelm M. schrieb:
>
1
> template<uintptr_t address>
2
> struct Port {
3
...
4
> };
5
> 
6
7
> using gp0 = Port<GPIOR0>;
8
> 
9
> int main() {
10
>     gp0::set<1>();
11
> }
12
>
>

Was mir jetzt daran nicht so sehr gefällt ist:

uintptr_t hätte ich gerne eingeschränkt auf die im Chip tatsächlich 
vorhanden Port Adressen ebenfalls die Pins.

gp0::set<1>(); ist mir ehrlich gesagt zu kryptisch ...

gp0::set<0>(); da muss man auch erst mal nachdenken was gemeint ist.

Was machen die beiden Funktionen setzen die jetzt einen ganzen Port auf 
1 oder 0 oder doch nur beide einen Pin auf 1 ich finde das nicht 
eindeutig.

Für Leute, die immer die neuesten Regeln von C++ im Kopf haben mag das 
eindeutig sein und für den Compiler ist es das auch.

von Wilhelm M. (wimalopaan)


Lesenswert?

Hans-Georg L. schrieb:
> Wilhelm M. schrieb:
>>
1
>> template<uintptr_t address>
2
>> struct Port {
3
> ...
4
>> };
5
>>
6
> 
7
>> using gp0 = Port<GPIOR0>;
8
>>
9
>> int main() {
10
>>     gp0::set<1>();
11
>> }
12
>>
>>
>
> Was mir jetzt daran nicht so sehr gefällt ist:
>
> uintptr_t hätte ich gerne eingeschränkt auf die im Chip tatsächlich
> vorhanden Port Adressen ebenfalls die Pins.

Schau Dir den Code an, den ich hier mal als Anhang gepostet hatte. Dort 
sind Meta-Funktionen drin, die tatsächlich nur für die vorhandenen 
internen Peripherie-Einheiten Adressen liefern. Für nicht vorhandenes 
liefern sie einen Compilezeit-Fehler.

Das obige war nur ein zusammengekochtes Beispiel für die "Anforderung" 
von Johann, die #defines aus der avr-libc als Template-Argument zu 
verwenden.

> gp0::set<1>(); ist mir ehrlich gesagt zu kryptisch ...

kannst ja ein
1
gpior0::setBit<1>();
oder
1
gpior0::set(Bit1);

oder, oder oder ... draus machen.

> Was machen die beiden Funktionen setzen die jetzt einen ganzen Port auf
> 1 oder 0 oder doch nur beide einen Pin auf 1 ich finde das nicht
> eindeutig.

Wie oben schon gesagt: es ist auch nur eine Erklärung für eine spezielle 
Frage gewesen, in der es gar nicht um die Elementfunktion set() ging, 
sondern um die Template-Argument.

von Einer K. (Gast)


Lesenswert?

Johann L. schrieb:
> Nur ein Scheinriese, wie in Jim Knopf.

Hmmm...

Wenn mich meine Erinnerung nicht trügt, war das doch ein gutmütiges, 
zurückhaltendes, Kerlchen.
Das möchte ich von unserem Priester, hier, nicht behaupten wollen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arduino F. schrieb:
> Johann L. schrieb:
>> Nur ein Scheinriese, wie in Jim Knopf.
>
> Wenn mich meine Erinnerung nicht trügt, war das doch ein
> gutmütiges, zurückhaltendes, Kerlchen.

Es ging mir dabei um das Gleichnis eines Scheinriesen:

Ein Problem oder eine Sache, die riesig erscheint.  Aber je mehr man 
sich ihm nähert (oder je mehr man darüber erfährt oder sich damit 
befasst) desto mehr schrumpft es zusammen, bis es schließlich nur noch 
ein kleiner Zwerg ist.

Beitrag #5252077 wurde von einem Moderator gelöscht.
Beitrag #5252078 wurde von einem Moderator gelöscht.
Beitrag #5252678 wurde von einem Moderator gelöscht.
Beitrag #5252681 wurde von einem Moderator gelöscht.
Beitrag #5252711 wurde von einem Moderator gelöscht.
Beitrag #5252714 wurde von einem Moderator gelöscht.
Beitrag #5252966 wurde von einem Moderator gelöscht.
Beitrag #5252968 wurde von einem Moderator gelöscht.
Beitrag #5253618 wurde von einem Moderator gelöscht.
Beitrag #5253619 wurde von einem Moderator gelöscht.
Beitrag #5253851 wurde von einem Moderator gelöscht.
Beitrag #5253854 wurde von einem Moderator gelöscht.
Beitrag #5254109 wurde von einem Moderator gelöscht.
Beitrag #5254110 wurde von einem Moderator gelöscht.
Beitrag #5254116 wurde von einem Moderator gelöscht.
Beitrag #5254123 wurde von einem Moderator gelöscht.
Beitrag #5254131 wurde von einem Moderator gelöscht.
von Carl D. (jcw2)


Lesenswert?

Johann L. schrieb:
> Arduino F. schrieb:
>> Johann L. schrieb:
>>> Nur ein Scheinriese, wie in Jim Knopf.
>>
>> Wenn mich meine Erinnerung nicht trügt, war das doch ein
>> gutmütiges, zurückhaltendes, Kerlchen.
>
> Es ging mir dabei um das Gleichnis eines Scheinriesen:
>
> Ein Problem oder eine Sache, die riesig erscheint.  Aber je mehr man
> sich ihm nähert (oder je mehr man darüber erfährt oder sich damit
> befasst) desto mehr schrumpft es zusammen, bis es schließlich nur noch
> ein kleiner Zwerg ist.

Wobei "unser" Scheinriese schon aus der Ferne ein Zwergchen ist.

Beitrag #5254140 wurde von einem Moderator gelöscht.
Beitrag #5254148 wurde von einem Moderator gelöscht.
Beitrag #5254155 wurde von einem Moderator gelöscht.
Beitrag #5254423 wurde von einem Moderator gelöscht.
Beitrag #5254428 wurde von einem Moderator gelöscht.
Beitrag #5255250 wurde von einem Moderator gelöscht.
Beitrag #5255251 wurde von einem Moderator gelöscht.
Beitrag #5256278 wurde von einem Moderator gelöscht.
Beitrag #5256280 wurde von einem Moderator gelöscht.
Beitrag #5257314 wurde von einem Moderator gelöscht.
Beitrag #5257319 wurde von einem Moderator gelöscht.
Beitrag #5258496 wurde von einem Moderator gelöscht.
Beitrag #5258497 wurde von einem Moderator gelöscht.
Beitrag #5258524 wurde von einem Moderator gelöscht.
Beitrag #5258538 wurde von einem Moderator gelöscht.
Beitrag #5259493 wurde von einem Moderator gelöscht.
Beitrag #5259502 wurde von einem Moderator gelöscht.
Beitrag #5259510 wurde von einem Moderator gelöscht.
Beitrag #5259516 wurde von einem Moderator gelöscht.
Beitrag #5259540 wurde von einem Moderator gelöscht.
von Jan K. (jan_k)


Lesenswert?

Hallo zusammen,

ich versuche gerade, in TMP reinzuschnuppern und mit 
https://github.com/kvasir-io/Kvasir etwas herumzuprobieren. Das ist echt 
eine verdammt abgefahrene Sache und ich frage mich, warum das nicht mehr 
verwendet wird.

Ist es zu kompliziert (sodass nur wenige Leute in der Lage sind, das zu 
verstehen?), ist es schlecht zu debuggen oder was sind die Gründe?

Ich hab mal eine ganz praktische Frage: Wie definiert ihr ein sauberes 
Interface in C++ (mit viel TMP oder zumindest class templates)? Immerhin 
liegt die gesamte Template Implementation öffentlich in den .hpp 
Headern?!

In C habe ich eine eine foo.h und eine foo.c Datei, in dem Header stehen 
nur öffentliche Funktionsprototypen drin, meistens mit einigen 
Kommentaren. Damit ist das öffentliche Interface schon fertig und 
übersichtlich. In (TMP) C++ erkenne ich aber häufig auf Anhieb gar 
nicht, welche Methoden und Typen es überhaupt gibt, weil eben viel 
Implementation im Header steht (stehen muss..).
In "klassischem" C++ geht die Datenkapselung/Information hiding 
eigentlich noch weiter, wird aber durch die Templates ad absurdum 
geführt, oder?

Weitere Frage an die C++ Benutzer: Wie definiert ihr Interfaces im 
Embedded Umfeld? Beispielsweise für einen Beschleunigungssensor und ein 
Gyroskop. Beide Sensoren werden regelmäßig gesampelt, können Daten 
zurückgeben, haben eine Schnittstelle über die sie angeschlossen sind 
und so weiter. Vereinheitlicht ihr solche Dinge in einem (virtuellen) 
Interface, das später im Code verwendet wird oder verzichtet ihr (aus 
Performancegründen?) komplett darauf? Wenn nicht, spielt das mit TMP 
zusammen?

Danke :)

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Jan K. schrieb:
> Hallo zusammen,
>
> ich versuche gerade, in TMP reinzuschnuppern und mit
> https://github.com/kvasir-io/Kvasir etwas herumzuprobieren. Das ist echt
> eine verdammt abgefahrene Sache und ich frage mich, warum das nicht mehr
> verwendet wird.
>
> Ist es zu kompliziert (sodass nur wenige Leute in der Lage sind, das zu
> verstehen?), ist es schlecht zu debuggen oder was sind die Gründe?

Ich will nicht sagen, dass es übermäßig kompliziert ist, allerdings ist 
es wohl sehr viel anders als üblicher OO-Code. Und ich glaube auch 
nicht, dass viele sich zunächst sofort vorstellen können, wozu man 
Meta-Funktionen, also Funktionen die mit Typen statt mit Objekten/Werten 
rechnen, braucht.

> Ich hab mal eine ganz praktische Frage: Wie definiert ihr ein sauberes
> Interface in C++ (mit viel TMP oder zumindest class templates)? Immerhin
> liegt die gesamte Template Implementation öffentlich in den .hpp
> Headern?!

Dir geht es bei der Frage um die Code-Strukturierung? Also kein 
<interface> im OOP-Sinne? Wenn ein template instanziiert (also ein 
konkreter Typ gebildet) werden soll, muss es vollständig bekannt sein. 
Das ist der Grund, warum templates (typischerweise) vollständig in 
Header-Files stehen. Dies wird sich erst mit C++20 und modules ändern.

> In C habe ich eine eine foo.h und eine foo.c Datei, in dem Header stehen
> nur öffentliche Funktionsprototypen drin, meistens mit einigen
> Kommentaren. Damit ist das öffentliche Interface schon fertig und
> übersichtlich. In (TMP) C++ erkenne ich aber häufig auf Anhieb gar
> nicht, welche Methoden und Typen es überhaupt gibt, weil eben viel
> Implementation im Header steht (stehen muss..).

Das hat gar nichts mit TMP, sondern einfach mit templates zu tun (s.o.). 
Du kannst aber nach wie vor, die Deklaration und die Definition von 
template-Elementen trennen. Ist zwar immer noch alles in der 
Header-Datei, aber abgesetzt voneinander und deswegen ggf. 
übersichtlicher.

> In "klassischem" C++ geht die Datenkapselung/Information hiding
> eigentlich noch weiter, wird aber durch die Templates ad absurdum
> geführt, oder?

Nein. Datenkapselung bedeutet ja nicht, wie man etwas auf Dateien 
aufteilt. Da hat man ja in C++ und C völlige Freiheit (anders als etwa 
bei Java). Auch in einem Template ist private, protected und public das 
was es in normalen Klassen auch ist. Möchte man die 
Template-Realisierung verstecken, so muss man auf vorinstanziierte 
Templates ausweichen, was aber ziemlicher Aufwand (und Murks) m.E. ist.

> Weitere Frage an die C++ Benutzer: Wie definiert ihr Interfaces im
> Embedded Umfeld? Beispielsweise für einen Beschleunigungssensor und ein
> Gyroskop. Beide Sensoren werden regelmäßig gesampelt, können Daten
> zurückgeben, haben eine Schnittstelle über die sie angeschlossen sind
> und so weiter. Vereinheitlicht ihr solche Dinge in einem (virtuellen)
> Interface, das später im Code verwendet wird oder verzichtet ihr (aus
> Performancegründen?) komplett darauf?

Das wäre der klassiche OOP Ansatz, also Laufzeit-Polymorphie.

> Wenn nicht, spielt das mit TMP
> zusammen?

Ich glaube, Du meinst hier nicht TMP, sondern einfach templates. Mit 
templates hat man statische Polymorphie. Das kann man mit 
Laufzeit-Polymorphie kombinieren, natürlich. Weil man aber die 
Laufzeitkosten der davon vermeiden möchte, bedient man sich der stat. 
Polymorphie.

TMP = template-meta-programmierung.

Darunter wird im allgemeinen das Erstellen von Meta-Funktionen 
verstanden. Die haben als Operanden i.A. Typen und bilden diese wieder 
auf Typen ab. Normale Funktionen operieren mit Werten. Daher der Name 
Meta-Funktion. Mit Meta-Funktionen "berechnet" man also Typ-Abbildungen 
Typ->Typ (oder Typ->Konstante, Konstante->Typ).

: Bearbeitet durch User
von Jan K. (jan_k)


Lesenswert?

Wilhelm M. schrieb:
>
> Ich will nicht sagen, dass es übermäßig kompliziert ist, allerdings ist
> es wohl sehr viel anders als üblicher OO-Code. Und ich glaube auch
> nicht, dass viele sich zunächst sofort vorstellen können, wozu man
> Meta-Funktionen, also Funktionen die mit Typen statt mit Objekten/Werten
> rechnen, braucht.
okay :)
>
>> Ich hab mal eine ganz praktische Frage: Wie definiert ihr ein sauberes
>> Interface in C++ (mit viel TMP oder zumindest class templates)? Immerhin
>> liegt die gesamte Template Implementation öffentlich in den .hpp
>> Headern?!
>
> Dir geht es bei der Frage um die Code-Strukturierung? Also kein
> <interface> im OOP-Sinne? Wenn ein template instanziiert (also ein
> konkreter Typ gebildet) werden soll, muss es vollständig bekannt sein.
> Das ist der Grund, warum templates (typischerweise) vollständig in
> Header-Files stehen. Dies wird sich erst mit C++20 und modules ändern.
Ja, kein Interface im Sinne einer pure virtual Klasse oder einem 
"interface" in C#. Ich meine hier die öffentliche Schnittstelle.
Warum Templates im Header stehen müssen verstehe ich, es ist ja nur eine 
Art Bauplan und die durch das Template generierte Klasse eines 
speziellen Typs ist eben noch nicht erstellt.

>> In C habe ich eine eine foo.h und eine foo.c Datei, in dem Header stehen
>> nur öffentliche Funktionsprototypen drin, meistens mit einigen
>> Kommentaren. Damit ist das öffentliche Interface schon fertig und
>> übersichtlich. In (TMP) C++ erkenne ich aber häufig auf Anhieb gar
>> nicht, welche Methoden und Typen es überhaupt gibt, weil eben viel
>> Implementation im Header steht (stehen muss..).
>
> Das hat gar nichts mit TMP, sondern einfach mit templates zu tun (s.o.).
Ja, korrekt, sorry. Es geht hier allgemein um Templates.
> Du kannst aber nach wie vor, die Deklaration und die Definition von
> template-Elementen trennen. Ist zwar immer noch alles in der
> Header-Datei, aber abgesetzt voneinander und deswegen ggf.
> übersichtlicher.
Also z.B. oben im Header irgendwo die (dokumentierten) Prototypen der 
Klasse auflisten und die Implementation weiter hinten.
>
>> In "klassischem" C++ geht die Datenkapselung/Information hiding
>> eigentlich noch weiter, wird aber durch die Templates ad absurdum
>> geführt, oder?
>
> Nein. Datenkapselung bedeutet ja nicht, wie man etwas auf Dateien
> aufteilt. Da hat man ja in C++ und C völlige Freiheit (anders als etwa
> bei Java). Auch in einem Template ist private, protected und public das
> was es in normalen Klassen auch ist. Möchte man die
> Template-Realisierung verstecken, so muss man auf vorinstanziierte
> Templates ausweichen, was aber ziemlicher Aufwand (und Murks) m.E. ist.
Okay, also aus OOP Sicht ist die Kapselung vorhanden, weil instanziierte 
Objekte einer (templatebasierten) Klasse dennoch nicht ihre Interna 
preisgeben?
Ich dachte bislang, das Verstecken der Implementation gehöre dazu, so im 
Sinne, "mir ist egal, wie das implementiert ist, ich muss nur die 
Prototypen kennen".
>
>> Weitere Frage an die C++ Benutzer: Wie definiert ihr Interfaces im
>> Embedded Umfeld? Beispielsweise für einen Beschleunigungssensor und ein
>> Gyroskop. Beide Sensoren werden regelmäßig gesampelt, können Daten
>> zurückgeben, haben eine Schnittstelle über die sie angeschlossen sind
>> und so weiter. Vereinheitlicht ihr solche Dinge in einem (virtuellen)
>> Interface, das später im Code verwendet wird oder verzichtet ihr (aus
>> Performancegründen?) komplett darauf?
>
> Das wäre der klassiche OOP Ansatz, also Laufzeit-Polymorphie.
Genau. Hast du oder jmd. anders hier Polymorphie zur Laufzeit produktiv 
eingesetzt? In C++ gibt es sonst keine anderen Möglichkeiten, Interfaces 
(diesmal wirklich im klassischen OOP Sinn, pure virtual Basisklasse in 
C++) zu definieren, oder? Vielleicht ist das aber auch eine Frage für 
einen eigenen Thread.
>
>> Wenn nicht, spielt das mit TMP
>> zusammen?
>
> Ich glaube, Du meinst hier nicht TMP, sondern einfach templates. Mit
> templates hat man statische Polymorphie. Das kann man mit
> Laufzeit-Polymorphie kombinieren, natürlich. Weil man aber die
> Laufzeitkosten der davon vermeiden möchte, bedient man sich der stat.
> Polymorphie.
Die Frage war zu weit gefasst, als dass hier klar geworden wäre was ich 
meinte. Bin mir gerade auch selbst nicht mehr sicher.
Mein Hauptanliegen ist: ich möchte - im embedded Umfeld - ein public 
interface als "Vertrag" für alle Entwickler definieren, gegen das sie 
dann programmieren können. So kann einer das Modul für den Gyro und der 
andere das Modul für den Beschleunigungssensor erstellen. Wie würdest 
du/ihr solch eine Schnittstelle definieren?

> TMP = template-meta-programmierung.
> ...
Werde mir das nochmal ganz genau zu Gemüte führen. Bisher habe ich das 
eher als Mechanismus zum Erstellen von zur Kompilierzeit bekannten 
(teils sehr komplexen, inklusive Fehlerbehandlung etc) Ausdrücken 
verstanden. Aber das ist möglicherweise nur eine Anwendung.

Vielen Dank für die Antworten!

von Wilhelm M. (wimalopaan)


Lesenswert?

Jan K. schrieb:

> Also z.B. oben im Header irgendwo die (dokumentierten) Prototypen der
> Klasse auflisten und die Implementation weiter hinten.

Ja.

>> Nein. Datenkapselung bedeutet ja nicht, wie man etwas auf Dateien
>> aufteilt. Da hat man ja in C++ und C völlige Freiheit (anders als etwa
>> bei Java). Auch in einem Template ist private, protected und public das
>> was es in normalen Klassen auch ist. Möchte man die
>> Template-Realisierung verstecken, so muss man auf vorinstanziierte
>> Templates ausweichen, was aber ziemlicher Aufwand (und Murks) m.E. ist.
> Okay, also aus OOP Sicht ist die Kapselung vorhanden, weil instanziierte
> Objekte einer (templatebasierten) Klasse dennoch nicht ihre Interna
> preisgeben?

Instanziierte Objekte? Du meinst instanziierte Templates? Oder das 
Exemplar einer Template-Klasse?

Wenn ein Klassentemplate instanziiert wird, wird ein konkreter Typ 
erstellt. Dies nennt man dann Templateklasse, um es von 
nicht-generischen Klassen zu unterscheiden. In dieser Templateklasse 
verhalten sich die Zugriffsarten private, protected und public ganz 
normal.

> Ich dachte bislang, das Verstecken der Implementation gehöre dazu, so im
> Sinne, "mir ist egal, wie das implementiert ist, ich muss nur die
> Prototypen kennen".

Man kann bei templates die Implementierung durch Code-Strukturierung 
nicht wirklich sinnvoll verstecken wie bei normalen Klassen. Das ist 
dann so ähnlich wie bei Java: sehe ich die Schnittstelle, sehe ich auch 
die Implementierung.

>>> Weitere Frage an die C++ Benutzer: Wie definiert ihr Interfaces im
>>> Embedded Umfeld? Beispielsweise für einen Beschleunigungssensor und ein
>>> Gyroskop. Beide Sensoren werden regelmäßig gesampelt, können Daten
>>> zurückgeben, haben eine Schnittstelle über die sie angeschlossen sind
>>> und so weiter. Vereinheitlicht ihr solche Dinge in einem (virtuellen)
>>> Interface, das später im Code verwendet wird oder verzichtet ihr (aus
>>> Performancegründen?) komplett darauf?

Du möchtest eine C++-Interface-Klasse schreiben? Der Weg geht ja über 
rein-virtuelle Elementfunktionen in einer Basisklasse. Damit haben wir 
ja in jedem Fall ein vtable ....
Aber: verwendest Du diese Typen auch tatsächlich beliebig polymorph? 
Oder ist das Interface nur eine Implementierungsvorgabe? Zudem: möchtest 
Du wirklich, das beliebig viele Objekte Deiner Klassen erstellt werden 
können, oder nur so viele, wie Du tatsächlich an dem µC angeschlossen 
hast (an konkreten Pins bzw. int. Peripherie).

Dann kannst Du dyn. Polymorphie durch stat. Polymorphie ersetzen (dazu 
kann man im übrigen auch heterogene Container, etwa std::tuple<> 
verwenden).

Wenn Du ein template hast (Dein periodischer Sampler), stellst Du 
implizite Typanforderungen an die Typ-Parameter (Dein Sensor). Erfüllt 
ein Typ-Parameter das nicht, scheitert die Compilierung mit "hübschen" 
Fehlermeldungen. Das kennen wir alle. Möchte man stattdessen die 
Anforderungen an einen Typ-Parameter explizit machen (wie ein 
Interface), so kann man das ab C++11 mit static_asserts und type-traits 
machen (umständlich) oder ab C++20 bzw. jetzt schon mit dem GCC ab 
Version 6.3 mit Constraints und Concepts. Man kann Concepts und die 
damit gebildeten Constraints als "Interface"-Anforderung für 
Template-Parameter auffassen.

>> Das wäre der klassiche OOP Ansatz, also Laufzeit-Polymorphie.
> Genau. Hast du oder jmd. anders hier Polymorphie zur Laufzeit produktiv
> eingesetzt?

Natürlich - alles ganz normal. Es gibt Situationen, damit kann/möchte 
man auf die dyn. Polymorphie nicht verzichten.

> In C++ gibt es sonst keine anderen Möglichkeiten, Interfaces
> (diesmal wirklich im klassischen OOP Sinn, pure virtual Basisklasse in
> C++) zu definieren, oder?

Doch: s.o.

> Mein Hauptanliegen ist: ich möchte - im embedded Umfeld - ein public
> interface als "Vertrag" für alle Entwickler definieren, gegen das sie
> dann programmieren können. So kann einer das Modul für den Gyro und der
> andere das Modul für den Beschleunigungssensor erstellen. Wie würdest
> du/ihr solch eine Schnittstelle definieren?

Ich würde ein Sampler-Template schreiben, das mit den Sensor-Typen 
parametrieren und die Anforderungen an die Sensor-Typen mit Constraints 
beschreiben, und diese Constraints können aus Concepts zusammengesetzt 
sein.

> Werde mir das nochmal ganz genau zu Gemüte führen. Bisher habe ich das
> eher als Mechanismus zum Erstellen von zur Kompilierzeit bekannten
> (teils sehr komplexen, inklusive Fehlerbehandlung etc) Ausdrücken
> verstanden.

Ausdrücke berechnen in C++ Werte (also Objekte, auch ein "int" ist ein 
Objekt). Dies kann man auch zur Compilezeit machen, wen möglich. Dies 
kann man in der Tat auch mit TMP durchführen, ist aber old school (so 
ist übrigens die turing-Vollständigkeit des template mechanismus 
"zufällig" entdeckt worden). Ab C++11/14/17 benutzt man dafür 
constexpr-functions bzw. callables, die durch constexpr 
lambda-expressions erzeugt werden.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.