6

Considerare una classe di puntatore intelligente basata su policy Ptr con una sola politica che impedirà il dereferenziamento dello stesso in uno stato NULL (in qualche modo). Prendiamo in considerazione 2 politiche di questo tipo:Come preservare l'implicità della costruzione in una classe basata su criteri

  • NotNull
  • NoChecking

Poiché la politica NotNull è più restrittiva, vorremmo consentire conversioni implicite Ptr< T, NoChecking >-Ptr< T, NotNull >, ma non in senso opposto direzione. Quello deve essere esplicito per sicurezza. Si prega di dare un'occhiata al seguente implementazione:

#include <iostream> 
#include <type_traits> 
#include <typeinfo> 

struct NoChecking; 
struct NotNull; 

struct NoChecking{ 
    NoChecking()     = default; 
    NoChecking(const NoChecking&) = default; 

    explicit NoChecking(const NotNull&) 
    { std::cout << "explicit conversion constructor of NoChecking" << std::endl; } 

protected: 
    ~NoChecking() {} //defaulting the destructor in GCC 4.8.1 makes it public somehow :o 
}; 

struct NotNull{ 
    NotNull()     = default; 
    NotNull(const NotNull&) = default; 

    NotNull(const NoChecking&) 
    { std::cout << "explicit conversion constructor of NotNull" << std::endl; } 

protected: 
    ~NotNull() {} 
}; 

template< 
    typename T, 
    class safety_policy 
> class Ptr 
: public safety_policy 
{ 
private: 
    T* pointee_; 

public: 
    template < 
    typename f_T, 
    class f_safety_policy 
    > friend class Ptr; //we need to access the pointee_ of other policies when converting 
         //so we befriend all specializations of Ptr 

    //implicit conversion operator 
    template< 
    class target_safety 
    > operator Ptr<T, target_safety>() const { 
    std::cout << "implicit conversion operator of " << typeid(*this).name() << std::endl; 

    static_assert(std::is_convertible<const safety_policy&, const target_safety&>::value, 
        //What is the condition to check? This requires constructibility 
        "Safety policy of *this is not implicitly convertible to target's safety policy."); 

     //calls the explicit conversion constructor of the target type 
    return Ptr< T, target_safety >(*this); 
    } 

    //explicit conversion constructor 
    template< 
    class target_safety 
    > explicit Ptr(const Ptr<T, target_safety>& other) 
    : safety_policy(other), //this is an explicit constructor call and will call explicit constructors when we make Ptr() constructor implicit! 
    pointee_(other.pointee_) 
    { std::cout << "explicit Ptr constructor of " << typeid(*this).name() << std::endl; } 

    Ptr() = default; 
}; 

    //also binds to temporaries from conversion operators 
void test_noChecking(const Ptr< int, NoChecking >&) 
{ } 

void test_notNull(const Ptr< int, NotNull >&) 
{ } 

int main() 
{ 
    Ptr< int, NotNull > notNullPtr;    //enforcing not null value not implemented for clarity 
    Ptr< int, NoChecking > fastPtr(notNullPtr);  //OK - calling explicit constructor and NotNull is explicitly convertible to NoChecking 

    test_notNull (fastPtr ); //should be OK - NoChecking is implictly convertible to NotNull 
    test_noChecking (notNullPtr); //should be ERROR - NotNull is explicitly convertible to NoChecking 

    return 0; 
} 

Live example

Il codice di cui sopra non è riuscita quando implicitamente la conversione in entrambe le direzioni, il che significa che std::is_convertible fallisce anche se le classi hanno costruttori compatibili. I problemi sono:

  1. Gli overload di costruttore non possono differire semplicemente dalla parola chiave esplicita, quindi abbiamo bisogno di un costruttore esplicito e di un operatore di conversione implicita (o viceversa) nella classe host.
  2. Il costruttore esplicito è migliore, poiché qualsiasi costruttore chiamerà costruttori espliciti dall'elenco di inizializzazione anche se è implicito stesso.
  3. L'operatore di conversione implicita non può creare oggetti di tipo di politica perché il loro distruttore è protetto. Questo è il motivo per cui std::is_convertible fallisce quando non dovrebbe, e questo è anche il motivo per cui non possiamo usare qualcosa come boost::implicit_cast< const target_policy& >(*this) nell'operatore di conversione, poiché creerebbe un oggetto di politica temporaneo, che è vietato.

Per quanto riguarda le soluzioni ovvie che non sono ottimali, a mio parere:

  1. Fai il distruttore politica pubblica - e UB rischio durante il lancio il Ptr * a * la politica e l'eliminazione di esso? Questo è improbabile nell'esempio fornito, ma è possibile nell'applicazione reale.
  2. Rendi pubblico il distruttore e utilizza l'ereditarietà protetta - Ho bisogno dell'interfaccia arricchita fornita dall'ereditarietà pubblica.

La domanda è:

Esiste una prova statica per l'esistenza del costruttore implicita da un tipo ad un altro che non crea oggetti di questo tipo?

O in alternativa:

Come si conservano le informazioni di costruzione implicita quando si chiama costruttori dalla classe host le politiche costruttore?


EDIT:

Ho appena realizzato che la seconda questione può essere facilmente risolta con un costruttore di implicita battente bandiera privato in questo modo:

#include <iostream> 
#include <type_traits> 
#include <typeinfo> 

struct implicit_flag {}; 

struct NoChecking; 
struct NotNull; 

struct NoChecking{ 
    NoChecking()     = default; 
    NoChecking(const NoChecking&) = default; 

protected: 
    explicit NoChecking(const NotNull&) 
    { std::cout << "explicit conversion constructor of NoChecking" << std::endl; } 


    ~NoChecking() {} //defaulting the destructor in GCC 4.8.1 makes it public somehow :o 
}; 

struct NotNull{ 
    NotNull()     = default; 
    NotNull(const NotNull&) = default; 

protected: 
    NotNull(implicit_flag, const NoChecking&) 
    { std::cout << "explicit conversion constructor of NotNull" << std::endl; } 

    ~NotNull() {} 
}; 

template< 
    typename T, 
    class safety_policy 
> class Ptr 
: public safety_policy 
{ 
private: 
    T* pointee_; 

public: 
    template < 
    typename f_T, 
    class f_safety_policy 
    > friend class Ptr; //we need to access the pointee_ of other policies when converting 
         //so we befriend all specializations of Ptr 

    //implicit conversion operator 
    template< 
    class target_safety 
    > operator Ptr<T, target_safety>() const { 
    std::cout << "implicit conversion operator of " << typeid(*this).name() << std::endl; 

    /*static_assert(std::is_convertible<const safety_policy&, const target_safety&>::value, //What is the condition to check? This requires constructibility 
        "Safety policy of *this is not implicitly convertible to target's safety policy.");*/ 

     //calls the explicit conversion constructor of the target type 
    return Ptr< T, target_safety >(implicit_flag(), *this); 
    } 

    //explicit conversion constructor 
    template< 
    class target_safety 
    > explicit Ptr(const Ptr<T, target_safety>& other) 
    : safety_policy(other), //this is an explicit constructor call and will not preserve the implicity of conversion! 
    pointee_(other.pointee_) 
    { std::cout << "explicit Ptr constructor of " << typeid(*this).name() << std::endl; } 

private: 

    //internal implicit-flagged constructor caller that is called from implicit conversion operator 
    template< 
    class target_safety 
    > Ptr(implicit_flag implicit, const Ptr<T, target_safety>& other) 
    : safety_policy(implicit, other), //this constructor is required in the policy 
    pointee_(other.pointee_) 
    { std::cout << "explicit Ptr constructor of " << typeid(*this).name() << std::endl; } 

public: 
    Ptr() = default; 
}; 

    //also binds to temporaries from conversion operators 
void test_noChecking(const Ptr< int, NoChecking >&) 
{ } 

void test_notNull(const Ptr< int, NotNull >&) 
{ } 

int main() 
{ 
    Ptr< int, NotNull > notNullPtr;    //enforcing not null value not implemented for clarity 
    Ptr< int, NoChecking > fastPtr(notNullPtr);  //OK - calling explicit constructor and NotNull is explicitly convertible to NoChecking 

    test_notNull (fastPtr ); //should be OK - NoChecking is implictly convertible to NotNull 
    test_noChecking (notNullPtr); //should be ERROR - NotNull is explicitly convertible to NoChecking 

    return 0; 
} 

Gli errori però sono non molto leggibile e introduciamo un requisito non necessario alle politiche, quindi una risposta alla prima domanda è più preferibile.

risposta

4

Vedi l'approccio "perfetto inizializzazione" presa da N4064 per std::pair e std::tuple che coinvolge test std::is_constructible<T, U>::value e std::is_convertible<U, T>::value

Se entrambi sono vere, v'è una conversione implicita, se solo il primo è vera la conversione è esplicito.

La soluzione è definire due overload per il costruttore, che è implicito e uno explicit, e utilizzare SFINAE modo che al massimo un sovraccarico è praticabile.

+0

'std :: is_constructible :: value' e viceversa sono falsi. È impossibile costruire un oggetto di tipo di politica a causa del distruttore privato, quindi non sono sicuro di come ciò possa essere d'aiuto. Trucco affascinante però. Sono sicuro che lo userò da qualche altra parte. – tsuki

0

Beh, mi ci è voluto un po 'di tempo per capire, ma se il problema sta nel fatto, non possiamo creare oggetti di tipo policy perché il suo distruttore è protected, quindi perché non lo ospitiamo in un temporaneo classe di inoltro?

Ptr implicito operatore di conversione:

template< 
    class target_safety 
    > operator Ptr<T, target_safety>() const { 
    std::cout << "implicit conversion operator of " << typeid(*this).name() << std::endl; 

    struct target_host : target_safety { using target_safety::target_safety; }; 

    static_assert(std::is_convertible<Ptr, target_host>::value, 
        //Now this works, because target_host is constructible! 
        "Safety policy of *this is not implicitly convertible to target's safety policy."); 

     //calls the explicit conversion constructor of the target type 
    return Ptr< T, target_safety >(*this); 
    } 

Live demonstration

Il trucco è di trasmettere i costruttori di target_policy a target_host, in modo che possa essere costruito da argomenti target_policy can. Poiché Ptr deriva da safety_policy, può essere convertito implicitamente in (const) safety_policy&(&). Ciò significa che la conversione di prova Ptr -> target_host equivale alla prova di costruzione target_host::target_safety(safety_policy).


Utilizzando il trucco di inizializzazione perfetto fornito da Jonathan Wakely in collaborazione con la politica temporanea di hosting possiamo risolverlo nel modo seguente:

#include <iostream> 
#include <type_traits> 
#include <typeinfo> 

template< typename Policy > 
struct policy_host_ 
: Policy 
{ 
    using Policy::Policy; 
}; 

template< typename Source, typename Target > 
struct is_implicitly_convertible 
: std::integral_constant< 
    bool 
    , std::is_constructible< policy_host_<Target>, policy_host_<Source> >::value && 
    std::is_convertible< policy_host_<Source>,policy_host_<Target> >::value 
    > 
{ }; 

template< typename Source, typename Target > 
struct is_explicitly_convertible 
: std::integral_constant< 
    bool 
    , std::is_constructible< policy_host_<Target>, policy_host_<Source> >::value && 
    !std::is_convertible< policy_host_<Source>,policy_host_<Target> >::value 
    > 
{ }; 

struct NoChecking; 
struct NotNull; 

struct NoChecking{ 
    NoChecking()     = default; 
    NoChecking(const NoChecking&) = default; 


    explicit NoChecking(const NotNull&) 
    { std::cout << "explicit conversion constructor of NoChecking" << std::endl; } 

protected: 
    ~NoChecking() {} //defaulting the destructor in GCC 4.8.1 makes it public somehow :o 
}; 

struct NotNull{ 
    NotNull()     = default; 
    NotNull(const NotNull&) = default; 


    NotNull(const NoChecking&) 
    { std::cout << "explicit conversion constructor of NotNull" << std::endl; } 

protected: 
    ~NotNull() {} 
}; 

template< 
    typename T, 
    class safety_policy 
> class Ptr 
: public safety_policy 
{ 
private: 
    T* pointee_; 

public: 
    template < 
    typename f_T, 
    class f_safety_policy 
    > friend class Ptr; //we need to access the pointee_ of other policies when converting 
         //so we befriend all specializations of Ptr 

    template< 
    class target_safety, 
    typename std::enable_if< 
     is_implicitly_convertible< target_safety, safety_policy >::value 
    , bool>::type = false 
    > Ptr(const Ptr<T, target_safety>& other) 
    : safety_policy(other), 
    pointee_(other.pointee_) 
    { std::cout << "implicit Ptr constructor of " << typeid(*this).name() << std::endl; } 

    template< 
    class target_safety, 
    typename std::enable_if< 
     is_explicitly_convertible< target_safety, safety_policy >::value 
    , bool>::type = false 
    > explicit Ptr(const Ptr<T, target_safety>& other) 
    : safety_policy(other), //this is an explicit constructor call and will not preserve the implicity of conversion! 
    pointee_(other.pointee_) 
    { std::cout << "explicit Ptr constructor of " << typeid(*this).name() << std::endl; } 

    Ptr() = default; 
}; 

    //also binds to temporaries from conversion operators 
void test_noChecking(const Ptr< int, NoChecking >&) 
{ } 

void test_notNull(const Ptr< int, NotNull >&) 
{ } 

int main() 
{ 
    Ptr< int, NotNull > notNullPtr;    //enforcing not null value not implemented for clarity 
    Ptr< int, NoChecking > fastPtr(notNullPtr);  //OK - calling explicit constructor and NotNull is explicitly convertible to NoChecking 

    test_notNull (fastPtr ); //should be OK - NoChecking is implictly convertible to NotNull 
    test_noChecking (notNullPtr); //should be ERROR - NotNull is explicitly convertible to NoChecking 

    return 0; 
} 

Live demo