2015-02-08 14 views
5

Vorrei utilizzare la composizione e scrivere buoni metodi di inoltro per ogni possibile sovraccarico (noxcept, const, volatile) utilizzando le funzionalità C++.Inoltro del metodo con composizione invece dell'ereditarietà (utilizzando i tratti del linguaggio C++)

L'idea è di utilizzare i tratti per determinare se un metodo è dichiarato {noxcept/const/volatile/ecc.} E di comportarsi di conseguenza.

Ecco un esempio di quello che vorrei realizzare:

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; 
    } 
}; 

Ecco quello che ho finora **:

// 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)...); 
} 

prega di notare che questa domanda è diversa da quella seguente uno, perché so che possiamo usare i tratti C++ per ispezionare un'interfaccia oggetto: Composition: using traits to avoid forwarding functions?

** ispirato da una discussione di commenti con @David S tono qui: When should I use C++ private inheritance?.

risposta

1

Iniziamo con la soluzione e la spieghiamo pezzo per pezzo.

#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, &&) 

Interno rappresenta il tipo di oggetto verso cui si sta inoltrando e inner rappresenta il nome. Qualificatori è la combinazione di const, volatile, & e & & di cui hai bisogno sulla funzione membro.

Le specifiche di nox sono sorprendentemente complicate solo perché è necessario gestire la chiamata di funzione e costruire il valore di ritorno. Se la funzione che stai inoltrando restituisce un riferimento, sai che è sicuro (i riferimenti sono sempre non costruibili dallo stesso tipo), ma se la funzione viene restituita in base al valore, devi assicurarti che il costruttore di movimento dell'oggetto non sia eccetto.

Siamo stati in grado di semplificare questo un po 'utilizzando un argomento modello predefinito return_type, altrimenti avremmo dovuto specificare il tipo restituito due volte.

Utilizziamo static_cast nel corpo della funzione per gestire correttamente l'aggiunta di qualificatori di riferimento e cv al tipo contenuto. Questo non viene automaticamente rilevato dai qualificatori di riferimento sulla funzione.

Utilizzando l'ereditarietà invece di composizione

Utilizzando l'ereditarietà privata, la soluzione sembra più simile a questo:

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

Questo ha il vantaggio di

  • leggibilità
  • più veloce la compilazione orari
  • Faster codice build di debug (niente a linea)
  • Non usando la vostra profondità di ricorsione constexpr
  • Non usando il vostro modello di istanza profondità
  • Lavorare con il ritorno tipi non-mobili per valore
  • Lavorare con l'inoltro ai costruttori
+0

Grazie per la tua ottima risposta! Ho imparato molto sulla meta-programmazione. Possiamo usarlo in questo modo? 'FORWARDING_MEMBER_FUNCTIONS (std :: decay :: type, inner, function)' E con il puntatore membro: 'FORWARDING_MEMBER_FUNCTIONS (std :: decay :: type, * ptr , funzione) ? –

+0

BTW, Cosa impedisce al C++ di fornire la stessa sintassi 'use inner.function;' per la composizione? –

+0

@Julien__ È possibile modificare la macro per accettare il tipo di parametro. Lo svantaggio è che è necessario che la dichiarazione della variabile si verifichi prima di richiamare FORWARDING_MEMBER_FUNCTIONS, mentre se si specifica esplicitamente il tipo, tale restrizione non è presente. Quale è meglio dipende da te. Probabilmente non vorresti usarlo con i puntatori, tuttavia, a causa dei qualificatori di riferimento. Niente impedisce a C++ di utilizzare la sintassi usando per inoltrare le funzioni dei membri, tranne per il fatto che dovresti convincere il comitato ad accettarlo. –

Problemi correlati