jaki jest przypadek użycia jawnego (bool)

24

C ++ 20 wprowadził jawne (bool), które warunkowo wybiera w czasie kompilacji, czy konstruktor jest jawny, czy nie.

Poniżej znajduje się przykład, który znalazłem tutaj .

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

Czy ktoś może mi powiedzieć inny przypadek explicit (bool)użycia niż użycie std::is_integral?

NKAR
źródło
1
Jednym z przykładów jest znacznie łatwiejsze wdrażanie konstruktorów warunkowo jawnych, takich jak te tuplez tą funkcją.
Pretorianian
1
Nie jest to prawidłowa odpowiedź, ale można również spojrzeć na motywację w dokumencie, który ją wprowadził: wg21.link/p0892
N. Shead
Przykład: (wraz z koncepcjami) zmniejsza wymaganą liczbę klas podstawowych w celu wdrożenia warunkowo dostarczonego warunkowo jawnego konstruktora kopii z 3 do 0.
LF

Odpowiedzi:

21

Sama motywacja znajduje się w artykule .

Istnieje potrzeba, aby konstruktory były warunkowo jawne. Oznacza to, że chcesz:

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

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

To pierwsze jest w porządku, te konstruktory są ukryte. Ale to drugie byłoby złe, ci konstruktorzy są explicit. W C ++ 17 (lub C ++ 20 z koncepcjami) jedynym sposobem na wykonanie tej pracy jest napisanie dwóch konstruktorów - jednego expliciti jednego nie:

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

Są one prawie całkowicie zduplikowane - a definicje tych konstruktorów byłyby identyczne.

Za pomocą explicit(bool)możesz napisać tylko jednego konstruktora - z warunkowo jawną częścią konstrukcji zlokalizowaną tylko na explicit-specifier:

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

To lepiej pasuje do zamiaru, jest o wiele mniej kodu do napisania i jest mniej pracy dla kompilatora podczas rozwiązywania problemu przeciążenia (ponieważ jest mniej konstruktorów do wyboru).

Barry
źródło
1
C ++ 20 zapewnia również możliwość zmiany enable_if_tczęści na ładniejsze i prostsze ograniczenie, prawdopodobnie przy użyciu pojęć. Ale to nie ma znaczenia w tym pytaniu.
aschepler
2

Innym możliwym zastosowaniem, jaki widzę, jest szablon variadic:

Ogólnie dobrze jest mieć domyślnie explicit dla konstruktora tylko jeden argument (chyba że pożądana jest konwersja).

więc

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

    // ...
};
Jarod42
źródło
0

Widziałem przypadek użycia explicitwarunku wymagającego warunku, kiedy dane wejściowe mogą być typu widoku (surowy wskaźnik std::string_view), który nowy obiekt zachowa po wywołaniu (tylko kopiowanie widoku, a nie tego, do którego się odnosi, pozostanie zależne od czas życia oglądanego obiektu) lub może to być typ podobny do wartości (przejmuje własność kopii, bez zewnętrznych zależności czasu życia).

W takiej sytuacji osoba wywołująca jest odpowiedzialna za utrzymanie oglądanego obiektu przy życiu (odbiorca jest właścicielem widoku, a nie obiektu oryginalnego), a konwersja nie powinna być wykonywana w sposób niejawny, ponieważ ułatwia to niejawnie utworzonemu obiektowi przeżyj oglądany obiekt. Natomiast w przypadku typów wartości nowy obiekt otrzyma własną kopię, więc chociaż kopia może być kosztowna, nie spowoduje błędu kodu, jeśli nastąpi niejawna konwersja.

ShadowRanger
źródło