Was ist der Anwendungsfall für explizite (bool)

24

In C ++ 20 wurde explizit (bool) eingeführt, das beim Kompilieren bedingt auswählt, ob ein Konstruktor explizit gemacht wird oder nicht.

Unten ist ein Beispiel, das ich hier gefunden habe .

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

Kann mir jemand einen anderen Verwendungszweck explicit (bool)als die Verwendung nennen std::is_integral?

NKAR
quelle
1
Ein Beispiel ist, dass es viel einfacher wird, bedingt explizite Konstruktoren wie die tuplemit dieser Funktion zu implementieren .
Prätorianer
1
Keine richtige Antwort, aber Sie können sich auch die Motivation in dem Artikel
N. Shead
Beispiel: Es reduziert (zusammen mit Konzepten) die erforderliche Anzahl von Basisklassen, um einen bedingt bereitgestellten bedingt expliziten Kopierkonstruktor von 3 auf 0 zu implementieren.
LF

Antworten:

21

Die Motivation selbst ist in der Zeitung zu sehen .

Konstruktoren müssen bedingt explizit gemacht werden. Das heißt, Sie wollen:

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

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

Ersteres ist in Ordnung, diese Konstruktoren sind implizit. Aber letzteres wäre schlecht, diese Konstruktoren sind es explicit. Mit C ++ 17 (oder C ++ 20 mit Konzepten) können Sie diese Funktion nur ausführen, indem Sie zwei Konstruktoren schreiben - einen explicitund einen nicht:

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

Diese sind fast vollständig dupliziert - und die Definitionen dieser Konstruktoren wären identisch.

Mit explicit(bool)können Sie einfach einen einzelnen Konstruktor schreiben - wobei der bedingt explizite Teil der Konstruktion nur auf den explicit-specifier beschränkt ist:

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

Dies entspricht der Absicht besser, ist viel weniger Code zum Schreiben und weniger Arbeit für den Compiler während der Überlastungsauflösung (da weniger Konstruktoren zur Auswahl stehen).

Barry
quelle
1
C ++ 20 bietet auch die Möglichkeit, das enable_if_tTeil in eine schönere und einfachere Einschränkung zu ändern , möglicherweise unter Verwendung von Konzepten. Aber das ist nicht der Punkt dieser Frage.
Aschepler
2

Eine andere mögliche Verwendung, die ich sehe, ist mit variadic Vorlage:

Standardmäßig ist es im Allgemeinen gut, explicitfür den Konstruktor nur ein Argument zu haben (es sei denn, die Konvertierung ist erwünscht).

damit

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

    // ...
};
Jarod42
quelle
0

Ich könnte einen Anwendungsfall für das explicitbedingte Erfordernis sehen , wenn die Eingabe ein ansichtsähnlicher Typ (Rohzeiger std::string_view) sein könnte, an dem das neue Objekt nach dem Aufruf festhalten wird (nur das Kopieren der Ansicht, nicht das, worauf es sich bezieht, bleibt abhängig die Lebensdauer des angezeigten Objekts) oder es kann sich um einen wertähnlichen Typ handeln (übernimmt den Besitz einer Kopie ohne externe Abhängigkeiten zur Lebensdauer).

In einer solchen Situation ist der Aufrufer dafür verantwortlich, das angezeigte Objekt am Leben zu erhalten (der Angerufene besitzt eine Ansicht, nicht das ursprüngliche Objekt), und die Konvertierung sollte nicht implizit erfolgen, da dies für das implizit erstellte Objekt zu einfach ist überleben das Objekt, das es sieht. Im Gegensatz dazu erhält das neue Objekt für Werttypen eine eigene Kopie, sodass die Kopie zwar teuer sein kann, der Code jedoch nicht falsch ist, wenn eine implizite Konvertierung erfolgt.

ShadowRanger
quelle