2017-09-18 2 views
7
template<class _Other1, 
    class _Other2, 
    class = enable_if_t<is_constructible<_Ty1, _Other1>::value 
        && is_constructible<_Ty2, _Other2>::value>, 
    enable_if_t<is_convertible<_Other1, _Ty1>::value 
      && is_convertible<_Other2, _Ty2>::value, int> = 0> 
    constexpr pair(pair<_Other1, _Other2>&& _Right) 
     _NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Other1>::value 
      && is_nothrow_constructible<_Ty2, _Other2>::value)) 
    : first(_STD forward<_Other1>(_Right.first)), 
     second(_STD forward<_Other2>(_Right.second)) 
    { // construct from moved compatible pair 
    } 

template<class _Other1, 
    class _Other2, 
    class = enable_if_t<is_constructible<_Ty1, _Other1>::value 
        && is_constructible<_Ty2, _Other2>::value>, 
    enable_if_t<!is_convertible<_Other1, _Ty1>::value 
      || !is_convertible<_Other2, _Ty2>::value, int> = 0> 
    constexpr explicit pair(pair<_Other1, _Other2>&& _Right) 
     _NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Other1>::value 
      && is_nothrow_constructible<_Ty2, _Other2>::value)) 
    : first(_STD forward<_Other1>(_Right.first)), 
     second(_STD forward<_Other2>(_Right.second)) 
    { // construct from moved compatible pair 
    } 

utilitaire fichier pour VS 2017 ligne 206, _Other1 et _Other2 sont des paramètres, c'est std :: paire construction » de Func, et nous utilisons Autres1 et Autres2 à initialiser "premier" et "deuxième",

Je pense que is_constructible est assez, pourquoi utilisons-nous is_convertible ici?
et en passant, quelle est la différence entre class = enable_if_t< ... ::value> et enable_if_t< ... ::value,int> = 0?Pourquoi 'is_convertible' ici <utility> std :: pair (STL)?

+0

Ceci est de mettre en œuvre [\ [paires.pair \]/12] (http://eel.is/c++draft/pairs.pair#12.sentence-2). – cpplearner

Répondre

9

Je pense is_constructible suffit, pourquoi nous utilisons is_convertible ici?

Le but ici est de gérer correctement la construction explicit. Pensez à faire que l'ancien et d'essayer d'écrire un wrapper (en utilisant REQUIRES ici pour cacher tout ce qui approche SFINAE vous voulez):

template <class T> 
class wrapper { 
public: 
    template <class U, REQUIRES(std::is_constructible<T, U&&>::value)> 
    wrapper(U&& u) : val(std::forward<U>(u)) { } 
private: 
    T val; 
}; 

Si c'est tout ce que nous avions, alors:

struct Imp { Imp(int); }; 
struct Exp { explicit Exp(int); }; 

Imp i = 0; // ok 
Exp e = 0; // error 
wrapper<Imp> wi = 0; // ok 
wrapper<Exp> we = 0; // ok?!? 

Nous ne facturons aucun doute Je ne veux pas que le dernier soit correct - cela brise l'attente pour Exp!

Maintenant, s_constructible<T, U&&> est vrai s'il est possible de diriger-initialiser un T d'un U&& - si T(std::declval<U&&>()) est une expression valide.

is_convertible<U&&, T>, d'autre part, vérifie s'il est possible de copier -initialize un T d'un U&&. C'est-à-dire, si T copy() { return std::declval<U&&>(); } est valide.

La différence est que celui-ci ne fonctionne pas si la conversion est explicit:

+-----+--------------------------+------------------------+ 
|  | is_constructible<T, int> | is_convertible<int, T> | 
+-----+--------------------------+------------------------+ 
| Imp |  true_type   |  true_type  | 
| Exp |  true_type   |  false_type  | 
+-----+--------------------------+------------------------+ 

Afin de propager correctement explicitation, nous devons utiliser les deux traits ensemble - et nous pouvons créer des méta-traits sur les:

template <class T, class From> 
using is_explicitly_constructible = std::integral_constant<bool, 
    std::is_constructible<T, From>::value && 
    !std::is_convertible<From, T>::value>; 

template <class T, class From> 
using is_implicitly_constructible = std::integral_constant<bool, 
    std::is_constructible<T, From>::value && 
    std::is_convertible<From, T>::value>; 

Ces deux traits sont disjoints, donc nous pouvons écrire deux modèles de constructeur qui ne sont certainement pas à la fois viable, où un constructeur est explicite et l'autre n'est pas:

template <class T> 
class wrapper { 
public: 
    template <class U, REQUIRES(is_explicitly_constructible<T, U&&>::value)> 
    explicit wrapper(U&& u) : val(std::forward<U>(u)) { } 

    template <class U, REQUIRES(is_implicitly_constructible<T, U&&>::value)> 
    wrapper(U&& u) : val(std::forward<U>(u)) { } 
private: 
    T val; 
}; 

Cela nous donne le comportement souhaité:

wrapper<Imp> wi = 0; // okay, calls non-explicit ctor 
wrapper<Exp> we = 0; // error 
wrapper<Exp> we2(0); // ok 

C'est ce que la mise en œuvre fait ici - sauf au lieu des deux méta-traits qu'ils ont toutes les conditions écrites sur explicit ment.

8

Pour la mise en œuvre [pairs.pair]/12:

Ce constructeur ne doit pas participer à la résolution de surcharge à moins is_­constructible_­v<first_­type, U1&&> est vrai et is_­constructible_­v<second_­type, U2&&> est vrai. Le constructeur est explicite si et seulement si is_­convertible_­v<U1&&, first_­type> est faux ou is_­convertible_­v<U2&&, second_­type> est faux.

+0

Mais * pourquoi * est-il supposé être '' explicite'' si l'un ou l'autre type ou les deux ne sont pas convertibles? –

+3

@ArneVogel La conversion implicite d'un type en un autre où la conversion est explicite serait autorisée si ce n'était pas pour la vérification 'std :: is_convertible_v'. – Rakete1111