2015-02-08 11 views
5

Ich würde gerne Komposition und schreiben gute Weiterleitungsmethoden für jede mögliche Überladung (noexcept, const, flüchtige) mit C++ Fähigkeiten.Method forwarding mit Zusammensetzung statt Vererbung (mit C++ - Traits)

Die Idee ist, Eigenschaften zu verwenden, um zu bestimmen, ob eine Methode deklariert wird {noexcept/const/volatile/etc.} und sich entsprechend zu verhalten. Hier

ist ein Beispiel dafür, was Ich mag würde erreichen:

struct User{  
    UsedObject& obj; 
    User(UsedObject& obj) : obj(obj) {} 

    FORWARD_METHOD(obj, get); //here is where the forwarding happens 
}; 

struct UsedObject{ 
    string m{"Hello\n"}; 

    string& get(double d){ 
     cout << "\tUsed :const not called...\n"; 
     return m; 
    } 
    const string& get(double d) const{ 
     cout << "\tUsed :const called...\n"; 
     return m; 
    } 
}; 

Hier ist, was ich bisher **:

// forward with noexcept attribute 
// I'm not 100% sure about : std::declval<std::add_lvalue_reference<decltype(obj)>::type 

template<typename... Args> 
constexpr decltype(auto) get(Args && ... args) 
noexcept(
     noexcept(std::declval<std::add_lvalue_reference<decltype(obj)>::type>().get( std::forward<Args>(args)... )) 
     and 
     std::is_nothrow_move_constructible<decltype(std::declval<std::add_lvalue_reference<decltype(obj)>::type>().get( std::forward<Args>(args)... ))>::value 
     ) 
{ 
    cout << "const called...\n"; 
    return obj.get(std::forward<Args>(args)...); 
} 

// forward with noexcept and const attributes 
// I'm not sure that this one behave properly. 

template<typename... Args> 
constexpr decltype(auto) get(Args && ... args) 
const noexcept(
     noexcept(std::declval< std::add_const<decltype(obj) &>::type >().get( std::forward<Args>(args)... )) 
     and 
     std::is_nothrow_move_constructible<decltype(std::declval< std::add_const<decltype(obj) &>::type >().get( std::forward<Args>(args)... ))>::value 
     ) 
{ 
    cout << "const not called...\n"; 
    using const_type = std::add_lvalue_reference<std::add_const<std::remove_reference<decltype(obj)>::type>::type>::type; 
    return const_cast<const_type>(obj).get(std::forward<Args>(args)...); 
} 

Bitte beachten Sie, dass diese Frage anders aus dem folgenden ein, weil ich weiß, dass wir C++ - Eigenschaften verwenden können, um eine Objektschnittstelle zu überprüfen: Composition: using traits to avoid forwarding functions?

** inspiriert durch einen Faden der Kommentare mit @David S Ton hier: When should I use C++ private inheritance?.

Antwort

1

Beginnen wir mit der Lösung und erklären Sie sie Stück für Stück.

#define FORWARDING_MEMBER_FUNCTION(Inner, inner, function, qualifiers) \ 
    template< \ 
     typename... Args, \ 
     typename return_type = decltype(std::declval<Inner qualifiers>().function(std::declval<Args &&>()...)) \ 
    > \ 
    constexpr decltype(auto) function(Args && ... args) qualifiers noexcept(\ 
     noexcept(std::declval<Inner qualifiers>().function(std::forward<Args>(args)...)) and \ 
     (\ 
      std::is_reference<return_type>::value or \ 
      std::is_nothrow_move_constructible<return_type>::value \ 
     ) \ 
    ) { \ 
     return static_cast<Inner qualifiers>(inner).function(std::forward<Args>(args)...); \ 
    } 

#define FORWARDING_MEMBER_FUNCTIONS_CV(Inner, inner, function, reference) \ 
    FORWARDING_MEMBER_FUNCTION(Inner, inner, function, reference) \ 
    FORWARDING_MEMBER_FUNCTION(Inner, inner, function, const reference) \ 
    FORWARDING_MEMBER_FUNCTION(Inner, inner, function, volatile reference) \ 
    FORWARDING_MEMBER_FUNCTION(Inner, inner, function, const volatile reference) 

#define FORWARDING_MEMBER_FUNCTIONS(Inner, inner, function) \ 
    FORWARDING_MEMBER_FUNCTIONS_CV(Inner, inner, function, &) \ 
    FORWARDING_MEMBER_FUNCTIONS_CV(Inner, inner, function, &&) 

Inner steht für den Typ des Objekts, an das Sie weiterleiten, und inner repräsentiert seinen Namen. Qualifiers ist die Kombination von const, volatile, & und & &, die Sie für Ihre Mitgliedsfunktion benötigen.

Die noexcept-Spezifikation ist überraschend kompliziert, nur weil Sie den Funktionsaufruf verarbeiten und den Rückgabewert konstruieren müssen. Wenn die Funktion, die Sie weiterleiten, eine Referenz zurückgibt, wissen Sie, dass sie sicher ist (Referenzen sind immer nicht vom selben Typ konstruierbar), aber wenn die Funktion durch Wert zurückgegeben wird, müssen Sie sicherstellen, dass der Move-Konstruktor des Objekts kein Excecept ist.

Wir konnten dies ein wenig vereinfachen, indem wir das default-Template-Argument return_type verwendeten, sonst hätten wir diesen Rückgabetyp zweimal buchstabieren müssen.

Wir verwenden den static_cast im Hauptteil der Funktion, um korrekt cv und Referenzqualifikatoren zum enthaltenen Typ hinzuzufügen. Dies wird nicht automatisch von Referenzqualifikationsmerkmalen der Funktion übernommen.

Vererbung Zusammensetzung anstelle der Verwendung von

private Vererbung verwenden, sieht die Lösung eher wie dieses:

struct Outer : private Inner { 
    using Inner::f; 
}; 

Dies hat den Vorteil von

  • Ablesbarkeit
  • Faster hat kompilieren mal
  • mit bis mit bis
  • Schnellen Code in Debug-Builds (nichts Inline)
  • Nicht Ihre constexpr Rekursionstiefe
  • Nicht Ihre Template-Instantiierung Tiefen
  • Arbeiten mit von Wert nicht bewegliche Typen Rückkehr
  • Arbeiten mit Weiterleitung an Erbauer
+0

Vielen Dank für Ihre sehr gute Antwort! Ich habe viel über Meta-Programmierung gelernt. Können wir es so benutzen? 'FORWARDING_MEMBER_FUNCTIONS (std :: zerfallen :: type, innere, Funktion)' Und mit Mitgliedszeiger: 'FORWARDING_MEMBER_FUNCTIONS (std :: zerfallen :: type, * ptr , Funktion); ' ? –

+0

BTW, Was hindert C++ daran, die gleiche Syntax 'use inner.function;' für die Komposition bereitzustellen? –

+0

@Julien__ Sie könnten das Makro ändern, um den Typ des Parameters zu akzeptieren. Der Nachteil ist, dass Sie Ihre Variablendeklaration vor dem Aufruf von FORWARDING_MEMBER_FUNCTIONS ausführen müssen. Wenn Sie den Typ explizit angeben, ist diese Einschränkung nicht vorhanden. Was besser ist, liegt an dir. Sie würden dies jedoch wahrscheinlich nicht mit Zeigern aufgrund von Referenzqualifikationsmerkmalen verwenden. Nichts hindert C++ daran, die Using-Syntax zur Weiterleitung an Funktionen von Mitgliedern zu verwenden, außer dass Sie das Komitee davon überzeugen müssen, es zu akzeptieren. –