15

Sto sperimentando le nuove funzionalità di C++ 11. Nella mia configurazione mi piacerebbe molto usare i costruttori ereditari, ma sfortunatamente nessun compilatore implementa ancora quelli. Pertanto sto cercando di simulare lo stesso comportamento. Posso scrivere qualcosa di simile:rilevamento di costruttori protetti di classe (possibilmente astratta)

template <class T> 
class Wrapper : public T { 
    public: 
    template <typename... As> 
    Wrapper(As && ... as) : T { std::forward<As>(as)... } { } 
    // ... nice additions to T ... 
}; 

Questo funziona ... la maggior parte del tempo. A volte il codice che utilizza le classi Wrapper deve utilizzare SFINAE per rilevare come è possibile costruire tale tipo di Wrapper<T>. Esiste tuttavia il seguente problema: per quanto riguarda la risoluzione del sovraccarico, il costruttore di Wrapper<T> accetta qualsiasi argomento, ma la compilazione non riesce (e questo è non coperto da SFINAE) se il tipo T non può essere costruito utilizzando quelli.

stavo cercando di attivare in modo condizionale le diverse istanze del modello costruttore utilizzando enable_if

template <typename... As, typename std::enable_if<std::is_constructible<T, As && ...>::value, int>::type = 0> 
    Wrapper(As && ... as) // ... 

che funziona bene fino a quando:

  • il costruttore appropriato di T è public
  • T non è astratto

La mia domanda è: come sbarazzarsi dei due precedenti vincoli?

ho cercato di superare il primo controllando (utilizzando SFINAE e sizeof()) se l'espressione new T(std::declval<As &&>()...) è ben formata all'internoWrapper<T>. Ma questo, ovviamente, non funziona, perché l'unico modo in cui una classe derivata può utilizzare il costruttore protetto della sua base è nell'elenco di inizializzazione dei membri.

Per il secondo, non ho la minima idea - ed è quello che ho bisogno di più, perché a volte è la Wrapper che implementa le funzioni astratte di T, rendendolo un tipo completo.

Voglio una soluzione che:...

  • è corretto secondo le normali
  • opere in nessuno dei gcc-4.6 *, gcc-4.7 * o clang-3 *

Grazie!

+0

Sono di fretta, ma forse http://stackoverflow.com/questions/8984013/can-sfinae-detect-private-access-violations possono essere d'aiuto in questo caso, non mi piacerebbe contare su gcc 4.6 per farlo bene anche se – PlasmaHH

+1

il controllo degli accessi è un po 'complicato qui: se usi 'sizeof()', il compilatore controllerà l'intera espressione, accesso incluso - ma poi l'accesso è controllato ** dal contesto dell'espressione **, che fallisce nel caso di costruttori protetti; tutto tranne "sizeof" funziona solo a livello di risoluzione del sovraccarico e di inferenza del tipo, quindi le violazioni di accesso non attiveranno SFINAE - ma poi, non vedo alcun modo di fare qualcosa con un costruttore, dato che non può essere passato come argomento modello . Per quanto riguarda il supporto del compilatore, sarò felice se ** qualsiasi ** di quanto sopra accetti il ​​codice. –

risposta

12

Questo sembra funzionare correttamente sul mio GCC locale (4.7, per gentile concessione di rubenvb). GCC su ideone stampa però molti errori interni del compilatore "implementati".

Ho dovuto effettuare i "dettagli di implementazione" del pubblico della classe Experiment, perché per alcuni motivi (che odora di bug), la mia versione di GCC si lamenta del fatto che siano private, anche se solo la classe stessa la utilizza.

#include <utility> 

template<typename T, typename Ignored> 
struct Ignore { typedef T type; }; 

struct EatAll { 
    template<typename ...T> 
    EatAll(T&&...) {} 
}; 

template<typename T> 
struct Experiment : T { 
public: 
    typedef char yes[1]; 
    typedef char no[2]; 

    static void check1(T const&); 
    static void check1(EatAll); 

    // if this SFINAE fails, T accepts it 
    template<typename ...U> 
    static auto check(int, U&&...u) 
    -> typename Ignore<no&, 
     decltype(Experiment::check1({std::forward<U>(u)...}))>::type; 

    template<typename ...U> 
    static yes &check(long, U&&...); 

public: 
    void f() {} 
    template<typename ...U, 
      typename std::enable_if< 
      std::is_same<decltype(Experiment::check(0, std::declval<U>()...)), 
          yes&>::value, int>::type = 0> 
    Experiment(U &&...u):T{ std::forward<U>(u)... } 
    {} 
}; 

// TEST 

struct AbstractBase { 
    protected: 
    AbstractBase(int, float); 
    virtual void f() = 0; 
}; 

struct Annoyer { Annoyer(int); }; 

void x(Experiment<AbstractBase>); 
void x(Annoyer); 

int main() { 
    x({42}); 
    x({42, 43.f}); 
} 

Aggiornamento: Il codice funziona anche su Clang.

+0

Uso molto astuto di ambiguità, devo ammettere. Fammi controllare se funziona nel mio ambiente. A proposito, come si comporta con i costruttori di basi private? –

+0

@GrzegorzHerman costruttori di basi private non vengono controllati. Devo ammettere :(Quindi penserà che sia possibile una conversione: –

+0

è quello che pensavo - ma quello è un problema minore per me, il più importante è farlo funzionare per le classi astratte :) –