quel est le cas d'utilisation pour explicite (bool)

24

C ++ 20 a introduit explicit (bool) qui sélectionne conditionnellement au moment de la compilation si un constructeur est rendu explicite ou non.

Voici un exemple que j'ai trouvé ici .

struct foo {

  // Specify non-integral types (strings, floats, etc.) require explicit construction.

  template <typename T>

  explicit(!std::is_integral_v<T>) foo(T) {}

};

foo a = 123; // OK

foo b = "123"; // ERROR: explicit constructor is not a candidate (explicit specifier evaluates to true)

foo c {"123"}; // OK

Quelqu'un peut-il me dire toute autre utilité explicit (bool)autre que l'utilisation std::is_integral?

NKAR
la source
1
Un exemple est qu'il devient beaucoup plus facile d'implémenter des constructeurs conditionnellement explicites comme ceux pour tuplecette fonctionnalité.
Prétorien
1
Pas une bonne réponse, mais vous pouvez également regarder la motivation dans le document qui l'a présentée: wg21.link/p0892
N. Shead
Exemple: il (avec les concepts) réduit le nombre requis de classes de base pour implémenter un constructeur de copie conditionnellement explicite fourni conditionnellement de 3 à 0.
LF

Réponses:

21

La motivation elle-même peut être vue dans le document .

Il est nécessaire de rendre les constructeurs explicites conditionnellement. Autrement dit, vous voulez:

pair<string, string> safe() {
    return {"meow", "purr"}; // ok
}

pair<vector<int>, vector<int>> unsafe() {
    return {11, 22}; // error
}

Le premier est très bien, ces constructeurs sont implicites. Mais ce dernier serait mauvais, ces constructeurs le sont explicit. Avec C ++ 17 (ou C ++ 20 avec des concepts), la seule façon de faire ce travail est d'écrire deux constructeurs - un explicitet pas un:

template <typename T1, typename T2>
struct pair {
    template <typename U1=T1, typename U2=T2,
        std::enable_if_t<
            std::is_constructible_v<T1, U1> &&
            std::is_constructible_v<T2, U2> &&
            std::is_convertible_v<U1, T1> &&
            std::is_convertible_v<U2, T2>
        , int> = 0>
    constexpr pair(U1&&, U2&& );

    template <typename U1=T1, typename U2=T2,
        std::enable_if_t<
            std::is_constructible_v<T1, U1> &&
            std::is_constructible_v<T2, U2> &&
            !(std::is_convertible_v<U1, T1> &&
              std::is_convertible_v<U2, T2>)
        , int> = 0>
    explicit constexpr pair(U1&&, U2&& );    
};  

Celles-ci sont presque entièrement dupliquées - et les définitions de ces constructeurs seraient identiques.

Avec explicit(bool), vous pouvez simplement écrire un seul constructeur - avec la partie conditionnellement explicite de la construction localisée juste au explicit-spécifiant:

template <typename T1, typename T2>
struct pair {
    template <typename U1=T1, typename U2=T2,
        std::enable_if_t<
            std::is_constructible_v<T1, U1> &&
            std::is_constructible_v<T2, U2>
        , int> = 0>
    explicit(!std::is_convertible_v<U1, T1> ||
        !std::is_convertible_v<U2, T2>)
    constexpr pair(U1&&, U2&& );   
};

Cela correspond mieux à l'intention, est beaucoup moins de code à écrire et demande moins de travail au compilateur pendant la résolution de surcharge (car il y a moins de constructeurs à choisir).

Barry
la source
1
C ++ 20 offre également la possibilité de changer la enable_if_tpièce en une contrainte plus jolie et plus simple, en utilisant éventuellement des concepts. Mais c'est à côté de l'objet de cette question.
aschepler
2

Une autre utilisation possible que je vois est avec un modèle variadic:

Il est généralement bon, par défaut, d'avoir explicitpour constructeur avec un seul argument (sauf si la conversion est souhaitée).

donc

struct Foo
{
    template <typename ... Ts>
    explicit(sizeof...(Ts) == 1) Foo(Ts&&...);

    // ...
};
Jarod42
la source
0

Je pourrais voir un cas d'utilisation pour exiger explicitconditionnellement lorsque l'entrée peut être un type de vue (pointeur brut, std::string_view) que le nouvel objet conservera après l'appel (copier uniquement la vue, pas ce à quoi elle se réfère, rester dépendant de la durée de vie de l'objet visualisé), ou il peut s'agir d'un type de valeur (prend possession d'une copie, sans dépendances de durée de vie externes).

Dans une situation comme celle-ci, l'appelant est responsable de maintenir vivant l'objet visualisé (l'appelé possède une vue, pas l'objet d'origine), et la conversion ne doit pas être effectuée implicitement, car cela rend trop facile la création implicite de l'objet survivre à l'objet qu'il voit. En revanche, pour les types de valeur, le nouvel objet recevra sa propre copie, donc bien que la copie puisse être coûteuse, elle ne rendra pas le code incorrect si une conversion implicite se produit.

ShadowRanger
la source