Sigslot ist eine reine Header-, Thread-sichere Implementierung von Signal-Slots für C++.
Das Hauptziel bestand darin, Boost.Signals2 zu ersetzen.
Abgesehen von den üblichen Funktionen bietet es
Sigslot ist Unit-getestet und sollte zuverlässig und stabil genug sein, um Boost Signals2 zu ersetzen.
Die Tests laufen sauber unter den Adressen-, Thread- und undefinierten Verhaltensbereinigungsprogrammen.
Viele Implementierungen erlauben Signalrückgabetypen, Sigslot nicht, weil ich keine Verwendung dafür habe. Wenn ich vom Gegenteil überzeugt werden kann, kann es sein, dass ich meine Meinung später ändere.
Es ist keine Kompilierung oder Installation erforderlich. Fügen Sie einfach sigslot/signal.hpp
ein und verwenden Sie es. Sigslot ist derzeit auf einen C++14-kompatiblen Compiler angewiesen, kann aber bei Bedarf auf C++11 nachgerüstet werden. Es ist bekannt, dass es mit den Compilern Clang 4.0 und GCC 5.0+ unter GNU Linux, MSVC 2017 und höher sowie Clang-cl und MinGW unter Windows funktioniert.
Beachten Sie jedoch, dass unter Windows mit MSVC- und Clang-Cl-Compilern ein potenzielles Problem besteht, das in Ausnahmesituationen möglicherweise die Linker-Flags /OPT:NOICF
benötigt. Eine Erklärung finden Sie im Kapitel „Implementierungsdetails“.
Zu Installationszwecken und zum Generieren eines CMake-Importmoduls wird eine CMake-Listendatei bereitgestellt. Dies ist die bevorzugte Installationsmethode. Das importierte Pal::Sigslot
-Ziel ist verfügbar und wendet bereits die erforderlichen Linker-Flags an. Es ist auch für Beispiele und Tests erforderlich, die optional für Adapter-Unit-Tests von Qt5 und Boost abhängen.
# Using Sigslot from cmake
find_package (PalSigslot)
add_executable (MyExe main.cpp)
target_link_libraries (MyExe PRIVATE Pal::Sigslot)
Zur Konfigurationszeit ist eine Konfigurationsoption SIGSLOT_REDUCE_COMPILE_TIME
verfügbar. Wenn es aktiviert ist, versucht es, das Aufblähen von Code zu reduzieren, indem es umfangreiche Vorlageninstanziierungen vermeidet, die aus Aufrufen von std::make_shared
resultieren. Diese Option ist standardmäßig deaktiviert, kann jedoch für diejenigen aktiviert werden, die Codegröße und Kompilierungszeit bevorzugen und gleichzeitig etwas weniger effizienten Code benötigen.
Die Installation kann mithilfe der folgenden Anweisungen aus dem Stammverzeichnis erfolgen:
mkdir build && cd build
cmake .. -DSIGSLOT_REDUCE_COMPILE_TIME=ON -DCMAKE_INSTALL_PREFIX= ~ /local
cmake --build . --target install
# If you want to compile examples:
cmake --build . --target sigslot-examples
# And compile/execute unit tests:
cmake --build . --target sigslot-tests
Pal::Sigslot
kann auch über die FetchContent-Methode eingebunden werden.
include (FetchContent)
FetchContent_Declare(
sigslot
GIT_REPOSITORY https://github.com/palacaze/sigslot
GIT_TAG 19a6f0f5ea11fc121fe67f81fd5e491f2d7a4637 # v1.2.0
)
FetchContent_MakeAvailable(sigslot)
add_executable (MyExe main.cpp)
target_link_libraries (MyExe PRIVATE Pal::Sigslot)
Sigslot implementiert das in UI-Frameworks beliebte Signal-Slot-Konstrukt und erleichtert so die Verwendung des Beobachtermusters oder der ereignisbasierten Programmierung. Der Haupteinstiegspunkt der Bibliothek ist die Klassenvorlage sigslot::signal<T...>
.
Ein Signal ist ein Objekt, das typisierte Benachrichtigungen ausgeben kann, also Werte, die nach den Parametern der Signalklassenvorlage parametrisiert sind, und eine beliebige Anzahl von Benachrichtigungshandlern (aufrufbaren Elementen) kompatibler Argumenttypen registrieren kann, die mit den bereitgestellten Werten ausgeführt werden, wenn eine Signalausgabe erfolgt. Im Signal-Slot-Sprachgebrauch wird dies als Verbinden eines Slots mit einem Signal bezeichnet, wobei ein „Slot“ eine aufrufbare Instanz darstellt und eine „Verbindung“ als konzeptionelle Verbindung von Signal zu Slot betrachtet werden kann.
Alle unten dargestellten Snippets sind im Beispielunterverzeichnis in kompilierbarer Quellcodeform verfügbar.
Hier ist ein erstes Beispiel, das die grundlegendsten Funktionen der Bibliothek zeigt.
Wir deklarieren zunächst ein parameterfreies Signal sig
, verbinden dann mehrere Slots und geben schließlich ein Signal aus, das den Aufruf aller zuvor verbundenen aufrufbaren Slots auslöst. Beachten Sie, wie die Bibliothek verschiedene Formen von Callables verarbeitet.
# include < sigslot/signal.hpp >
# include < iostream >
void f () { std::cout << " free function n " ; }
struct s {
void m () { std::cout << " member function n " ; }
static void sm () { std::cout << " static member function n " ; }
};
struct o {
void operator ()() { std::cout << " function object n " ; }
};
int main () {
s d;
auto lambda = []() { std::cout << " lambda n " ; };
auto gen_lambda = []( auto && ... a ) { std::cout << " generic lambda n " ; };
// declare a signal instance with no arguments
sigslot:: signal <> sig;
// connect slots
sig. connect (f);
sig. connect (&s::m, &d);
sig. connect (&s::sm);
sig. connect ( o ());
sig. connect (lambda);
sig. connect (gen_lambda);
// a free connect() function is also available
sigslot::connect (sig, f);
// emit a signal
sig ();
}
Standardmäßig ist die Slot-Aufrufreihenfolge beim Aussenden eines Signals nicht festgelegt. Verlassen Sie sich bitte nicht darauf, dass sie immer gleich ist. Sie können eine bestimmte Aufrufreihenfolge einschränken, indem Sie Slot-Gruppen verwenden, die später vorgestellt werden.
Das erste Beispiel war einfach, aber nicht so nützlich. Gehen wir stattdessen zu einem Signal über, das Werte ausgibt. Ein Signal kann unten eine beliebige Anzahl von Argumenten ausgeben.
# include < sigslot/signal.hpp >
# include < iostream >
# include < string >
struct foo {
// Notice how we accept a double as first argument here.
// This is fine because float is convertible to double.
// 's' is a reference and can thus be modified.
void bar ( double d, int i, bool b, std::string &s) {
s = b ? std::to_string (i) : std::to_string (d);
}
};
// Function objects can cope with default arguments and overloading.
// It does not work with static and member functions.
struct obj {
void operator ()( float , int , bool , std::string &, int = 0 ) {
std::cout << " I was here n " ;
}
void operator ()() {}
};
int main () {
// declare a signal with float, int, bool and string& arguments
sigslot:: signal < float , int , bool , std::string&> sig;
// a generic lambda that prints its arguments to stdout
auto printer = [] ( auto a, auto && ... args ) {
std::cout << a;
( void )std::initializer_list< int >{
(( void )(std::cout << " " << args), 1 )...
};
std::cout << " n " ;
};
// connect the slots
foo ff;
sig. connect (printer);
sig. connect (&foo::bar, &ff);
sig. connect ( obj ());
float f = 1 . f ;
short i = 2 ; // convertible to int
std::string s = " 0 " ;
// emit a signal
sig (f, i, false , s);
sig (f, i, true , s);
}
Wie gezeigt, müssen die Slot-Argumententypen nicht unbedingt mit den Signalvorlagenparametern identisch sein, es ist in Ordnung, aus ihnen konvertierbar zu sein. Generische Argumente sind ebenfalls in Ordnung, wie das generische printer
Lambda zeigt (das auch als Funktionsvorlage hätte geschrieben werden können).
Im Moment fallen mir in Bezug auf die aufrufbare Handhabung zwei Einschränkungen ein: Standardargumente und Funktionsüberladung. Bei Funktionsobjekten funktionieren beide ordnungsgemäß, die Kompilierung mit statischen Funktionen und Mitgliedsfunktionen schlägt jedoch aus unterschiedlichen, aber zusammenhängenden Gründen fehl.
Betrachten Sie den folgenden Codeabschnitt:
struct foo {
void bar ( double d);
void bar ();
};
Worauf sollte sich &foo::bar
beziehen? Aufgrund der Überladung wird dieser Zeiger auf die Memberfunktion keinem eindeutigen Symbol zugeordnet, sodass der Compiler nicht in der Lage ist, das richtige Symbol auszuwählen. Eine Möglichkeit, das richtige Symbol aufzulösen, besteht darin, den Funktionszeiger explizit auf den richtigen Funktionstyp umzuwandeln. Hier ist ein Beispiel, das genau das tut, indem es ein kleines Hilfstool für eine einfachere Syntax verwendet (tatsächlich werde ich es wahrscheinlich bald zur Bibliothek hinzufügen).
# include < sigslot/signal.hpp >
template < typename ... Args, typename C>
constexpr auto overload ( void (C::*ptr)(Args...)) {
return ptr;
}
template < typename ... Args>
constexpr auto overload ( void (*ptr)(Args...)) {
return ptr;
}
struct obj {
void operator ()( int ) const {}
void operator ()() {}
};
struct foo {
void bar ( int ) {}
void bar () {}
static void baz ( int ) {}
static void baz () {}
};
void moo ( int ) {}
void moo () {}
int main () {
sigslot:: signal < int > sig;
// connect the slots, casting to the right overload if necessary
foo ff;
sig. connect (overload< int >(&foo::bar), &ff);
sig. connect (overload< int >(&foo::baz));
sig. connect (overload< int >(&moo));
sig. connect ( obj ());
sig ( 0 );
return 0 ;
}
Standardargumente sind nicht Teil der Funktionstypsignatur und können neu definiert werden, sodass sie wirklich schwierig zu handhaben sind. Beim Verbinden eines Slots mit einem Signal ermittelt die Bibliothek, ob das bereitgestellte Callable mit den Signalargumenttypen aufgerufen werden kann. Zu diesem Zeitpunkt ist jedoch nicht bekannt, ob Standardfunktionsargumente vorhanden sind, sodass möglicherweise eine Nichtübereinstimmung in der Anzahl der Argumente vorliegt.
Eine einfache Lösung für diesen Anwendungsfall wäre die Erstellung eines Bind-Adapters. Tatsächlich können wir ihn sogar ganz allgemein gestalten, etwa so:
# include < sigslot/signal.hpp >
# define ADAPT ( func )
[=]( auto && ...a) { (func)(std::forward< decltype (a)>(a)...); }
void foo ( int &i, int b = 1 ) {
i += b;
}
int main () {
int i = 0 ;
// fine, all the arguments are handled
sigslot:: signal < int &, int > sig1;
sig1. connect (foo);
sig1 (i, 2 );
// must wrap in an adapter
i = 0 ;
sigslot:: signal < int &> sig2;
sig2. connect ( ADAPT (foo));
sig2 (i);
return 0 ;
}
Was bisher nicht klar war, ist, dass signal::connect()
tatsächlich ein sigslot::connection
Objekt zurückgibt, das zur Verwaltung des Verhaltens und der Lebensdauer einer Signal-Slot-Verbindung verwendet werden kann. sigslot::connection
ist ein leichtgewichtiges Objekt (im Grunde ein std::weak_ptr
), das die Interaktion mit einer laufenden Signal-Slot-Verbindung ermöglicht und die folgenden Funktionen verfügbar macht:
signal::connect()
hergestellten Verbindung. Eine sigslot::connection
bindet keine Verbindung an einen Bereich: Dies ist kein RAII-Objekt, was erklärt, warum es kopiert werden kann. Es kann jedoch implizit in eine sigslot::scoped_connection
umgewandelt werden, die die Verbindung zerstört, wenn sie den Gültigkeitsbereich verlässt.
Hier ist ein Beispiel, das einige dieser Funktionen veranschaulicht:
# include < sigslot/signal.hpp >
# include < string >
int i = 0 ;
void f () { i += 1 ; }
int main () {
sigslot:: signal <> sig;
// keep a sigslot::connection object
auto c1 = sig. connect (f);
// disconnection
sig (); // i == 1
c1. disconnect ();
sig (); // i == 1
// scope based disconnection
{
sigslot::scoped_connection sc = sig. connect (f);
sig (); // i == 2
}
sig (); // i == 2;
// connection blocking
auto c2 = sig. connect (f);
sig (); // i == 3
c2. block ();
sig (); // i == 3
c2. unblock ();
sig (); // i == 4
}
Sigslot unterstützt eine erweiterte Slot-Signatur mit einer zusätzlichen sigslot::connection
Referenz als erstes Argument, die die Verbindungsverwaltung innerhalb des Slots ermöglicht. Auf diese erweiterte Signatur kann über die Methode connect_extended()
zugegriffen werden.
# include < sigslot/signal.hpp >
int main () {
int i = 0 ;
sigslot:: signal <> sig;
// extended connection
auto f = []( auto &con) {
i += 1 ; // do work
con. disconnect (); // then disconnects
};
sig. connect_extended (f);
sig (); // i == 1
sig (); // i == 1 because f was disconnected
}
Der Benutzer muss sicherstellen, dass die Lebensdauer eines Steckplatzes die eines Signals überschreitet, was bei komplexer Software mühsam sein kann. Um diese Aufgabe zu vereinfachen, kann Sigslot Slot-Objekte, deren Lebensdauer es verfolgen kann, automatisch trennen. Um dies zu erreichen, muss der Slot in einen schwachen Zeiger irgendeiner Form umgewandelt werden können.
std::shared_ptr
und std::weak_ptr
werden standardmäßig unterstützt, und es werden Adapter zur Unterstützung von boost::shared_ptr
, boost::weak_ptr
und Qt QSharedPointer
, QWeakPointer
und allen von QObject
abgeleiteten Klassen bereitgestellt.
Andere nachverfolgbare Objekte können durch Deklaration einer to_weak()
Adapterfunktion hinzugefügt werden.
# include < sigslot/signal.hpp >
# include < sigslot/adapter/qt.hpp >
int sum = 0 ;
struct s {
void f ( int i) { sum += i; }
};
class MyObject : public QObject {
Q_OBJECT
public:
void add ( int i) const { sum += i; }
};
int main () {
sum = 0 ;
signal < int > sig;
// track lifetime of object and also connect to a member function
auto p = std::make_shared<s>();
sig. connect (&s::f, p);
sig ( 1 ); // sum == 1
p. reset ();
sig ( 1 ); // sum == 1
// track an unrelated object lifetime
struct dummy ;
auto l = [&]( int i) { sum += i; };
auto d = std::make_shared<dummy>();
sig. connect (l, d);
sig ( 1 ); // sum == 2
d. reset ();
sig ( 1 ); // sum == 2
// track a QObject
{
MyObject o;
sig. connect (&MyObject::add, &o);
sig ( 1 ); // sum == 3
}
sig ( 1 ); // sum == 3
}
Eine weitere Möglichkeit, die automatische Trennung von Zeigern über Mitgliedsfunktionsslots sicherzustellen, besteht darin, explizit von sigslot::observer
oder sigslot::observer_st
zu erben. Ersteres ist im Gegensatz zu Letzterem threadsicher.
Hier ist ein Beispiel für die Verwendung.
# include < sigslot/signal.hpp >
int sum = 0 ;
struct s : sigslot::observer_st {
void f ( int i) { sum += i; }
};
struct s_mt : sigslot::observer {
~s_mt () {
// Needed to ensure proper disconnection prior to object destruction
// in multithreaded contexts.
this -> disconnect_all ();
}
void f ( int i) { sum += i; }
};
int main () {
sum = 0 ;
signal < int > sig;
{
// Lifetime of object instance p is tracked
s p;
s_mt pm;
sig. connect (&s::f, &p);
sig. connect (&s_mt::f, &pm);
sig ( 1 ); // sum == 2
}
// The slots got disconnected at instance destruction
sig ( 1 ); // sum == 2
}
Die Objekte, die diesen aufdringlichen Ansatz verwenden, können mit einer beliebigen Anzahl unabhängiger Signale verbunden sein.
In Version 1.2.0 wurde die Unterstützung der Slot-Trennung durch Bereitstellung einer entsprechenden Funktionssignatur, eines Objektzeigers oder eines Trackers eingeführt.
Mit der Methode signal::disconnect()
kann man beliebig viele Slots trennen, die 4 Überladungen zur Angabe des Trennkriteriums vorschlägt:
Die Trennung von Lambdas ist aufgrund ihrer Einzigartigkeit nur für Lambdas möglich, die an eine Variable gebunden sind.
Die zweite Überladung benötigt derzeit RTTI, um die Verbindung zu Zeigern auf Mitgliedsfunktionen, Funktionsobjekten und Lambdas zu trennen. Diese Einschränkung gilt nicht für freie und statische Memberfunktionen. Der Grund liegt in der Tatsache, dass in C++ Zeiger auf Memberfunktionen nicht verwandter Typen nicht vergleichbar sind, im Gegensatz zu Zeigern auf freie und statische Memberfunktionen. Beispielsweise kann der Zeiger auf Mitgliedsfunktionen virtueller Methoden verschiedener Klassen dieselbe Adresse haben (sie speichern sozusagen den Offset der Methode in der vtable).
Allerdings kann Sigslot mit deaktiviertem RTTI kompiliert werden und die Überlastung wird in problematischen Fällen deaktiviert.
Als Nebenknoten hat diese Funktion zugegebenermaßen mehr Code hinzugefügt als zunächst erwartet, da es schwierig ist und leicht etwas falsch macht. Es wurde sorgfältig und mit Blick auf Korrektheit entworfen und hat keine versteckten Kosten, es sei denn, Sie nutzen es tatsächlich.
Hier ist ein Beispiel, das die Funktion demonstriert.
# include < sigslot/signal.hpp >
# include < string >
static int i = 0 ;
void f1 () { i += 1 ; }
void f2 () { i += 1 ; }
struct s {
void m1 () { i += 1 ; }
void m2 () { i += 1 ; }
void m3 () { i += 1 ; }
};
struct o {
void operator ()() { i += 1 ; }
};
int main () {
sigslot:: signal <> sig;
s s1;
auto s2 = std::make_shared<s>();
auto lbd = [&] { i += 1 ; };
sig. connect (f1); // #1
sig. connect (f2); // #2
sig. connect (&s::m1, &s1); // #3
sig. connect (&s::m2, &s1); // #4
sig. connect (&s::m3, &s1); // #5
sig. connect (&s::m1, s2); // #6
sig. connect (&s::m2, s2); // #7
sig. connect (o{}); // #8
sig. connect (lbd); // #9
sig (); // i == 9
sig. disconnect (f2); // #2 is removed
sig. disconnect (&s::m1); // #3 and #6 are removed
sig. disconnect (o{}); // #8 and is removed
// sig.disconnect(&o::operator()); // same as the above, more efficient
sig. disconnect (lbd); // #9 and is removed
sig. disconnect (s2); // #7 is removed
sig. disconnect (&s::m3, &s1); // #5 is removed, not #4
sig (); // i == 11
sig. disconnect_all (); // remove all remaining slots
return 0 ;
}
Ab Version 1.2.0 kann Slots eine Gruppen-ID zugewiesen werden, um die relative Reihenfolge des Slot-Aufrufs zu steuern.
Die Reihenfolge des Aufrufs von Slots in derselben Gruppe ist nicht spezifiziert und sollte nicht als verlässlich angesehen werden. Slot-Gruppen werden jedoch in aufsteigender Reihenfolge der Gruppen-IDs aufgerufen. Wenn die Gruppen-ID eines Steckplatzes nicht festgelegt ist, wird er der Gruppe 0 zugewiesen. Gruppen-IDs können jeden Wert im Bereich vorzeichenbehafteter 32-Bit-Ganzzahlen haben.
# include < sigslot/signal.hpp >
# include < cstdio >
# include < limits >
int main () {
sigslot:: signal <> sig;
// simply assigning a group id as last argument to connect
sig. connect ([] { std::puts ( " Second " ); }, 1 );
sig. connect ([] { std::puts ( " Last " ); }, std::numeric_limits<sigslot::group_id>:: max ());
sig. connect ([] { std::puts ( " First " ); }, - 10 );
sig ();
return 0 ;
}
Die freistehende Funktion sigslot::connect()
kann verwendet werden, um ein Signal mit kompatiblen Argumenten mit einem anderen zu verbinden.
# include < sigslot/signal.hpp >
# include < iostream >
int main () {
sigslot:: signal < int > sig1;
sigslot:: signal < double > sig2;
sigslot::connect (sig1, sig2);
sigslot::connect (sig2, [] ( double d) { std::cout << " got " << d << std::endl; });
sig ( 1 );
return 0 ;
}
Die Thread-Sicherheit wird einheitlich getestet. Insbesondere die signalübergreifende Emission und die rekursive Emission funktionieren in einem Szenario mit mehreren Threads problemlos.
sigslot::signal
ist eine Typdefinition für die allgemeinere Vorlagenklasse sigslot::signal_base
, deren erstes Vorlagenargument ein sperrbarer Typ sein muss. Dieser Typ bestimmt die Sperrrichtlinie der Klasse.
Sigslot bietet 2 Typedefs,
sigslot::signal
kann von mehreren Threads aus verwendet werden und verwendet std::mutex als sperrbares Element. Insbesondere sind Verbindungs-, Trennungs-, Emissions- und Slot-Ausführung threadsicher. Es ist auch bei rekursiver Signalausgabe sicher.sigslot::signal_st
ist eine nicht threadsichere Alternative, sie tauscht Sicherheit gegen etwas schnelleren Betrieb ein. Der Vergleich von Funktionszeigern ist in C++ ein Albtraum. Hier ist eine Tabelle, die den Umfang und die Adresse verschiedener Fälle als Schaufenster zeigt:
void fun () {}
struct b1 {
virtual ~b1 () = default ;
static void sm () {}
void m () {}
virtual void vm () {}
};
struct b2 {
virtual ~b2 () = default ;
static void sm () {}
void m () {}
virtual void vm () {}
};
struct c {
virtual ~c () = default ;
virtual void w () {}
};
struct d : b1 {
static void sm () {}
void m () {}
void vm () override {}
};
struct e : b1, c {
static void sm () {}
void m () {}
void vm () override {}
};
Symbol | GCC 9 Linux 64 Größevon | GCC 9 Linux 64 Adresse | MSVC 16.6 32 Größevon | MSVC 16.6 32 Adresse | GCC 8 Mingw 32 Größevon | GCC 8 Mingw 32 Adresse | Clang-cl 9 32 Größevon | Clang-cl 9 32 Adresse |
---|---|---|---|---|---|---|---|---|
Spaß | 8 | 0x802340 | 4 | 0x1311A6 | 4 | 0xF41540 | 4 | 0x0010AE |
&b1::sm | 8 | 0xE03140 | 4 | 0x7612A5 | 4 | 0x308D40 | 4 | 0x0010AE |
&b1::m | 16 | 0xF03240 | 4 | 0x1514A5 | 8 | 0x248D40 | 4 | 0x0010AE |
&b1::vm | 16 | 0x11 | 4 | 0x9F11A5 | 8 | 0x09 | 4 | 0x8023AE |
&b2::sm | 8 | 0x003340 | 4 | 0xA515A5 | 4 | 0x408D40 | 4 | 0x0010AE |
&b2::m | 16 | 0x103440 | 4 | 0xEB10A5 | 8 | 0x348D40 | 4 | 0x0010AE |
&b2::vm | 16 | 0x11 | 4 | 0x6A14A5 | 8 | 0x09 | 4 | 0x8023AE |
&d::sm | 8 | 0x203440 | 4 | 0x2612A5 | 4 | 0x108D40 | 4 | 0x0010AE |
&dm | 16 | 0x303540 | 4 | 0x9D13A5 | 8 | 0x048D40 | 4 | 0x0010AE |
&d::vm | 16 | 0x11 | 4 | 0x4412A5 | 8 | 0x09 | 4 | 0x8023AE |
&e::sm | 8 | 0x403540 | 4 | 0xF911A5 | 4 | 0x208D40 | 4 | 0x0010AE |
&e::m | 16 | 0x503640 | 8 | 0x8111A5 | 8 | 0x148D40 | 8 | 0x0010AE |
&e::vm | 16 | 0x11 | 8 | 0xA911A5 | 8 | 0x09 | 8 | 0x8023AE |
MSVC und Clang-cl im Release-Modus optimieren Funktionen mit derselben Definition, indem sie sie zusammenführen. Dies ist ein Verhalten, das mit der Linkeroption /OPT:NOICF
deaktiviert werden kann. Sigslot-Tests und -Beispiele basieren auf vielen identischen Callables, die dieses Verhalten auslösen, weshalb diese spezielle Optimierung auf den betroffenen Compilern deaktiviert wird.
Die Verwendung generischer Lambdas mit GCC vor Version 7.4 kann Fehler Nr. 68071 auslösen.