¿Cuál es el caso de uso para explícito (bool)

24

C ++ 20 introdujo explícito (bool) que selecciona condicionalmente en tiempo de compilación si un constructor se hace explícito o no.

A continuación se muestra un ejemplo que encontré aquí .

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

¿Alguien puede decirme algún otro caso explicit (bool)de uso que no sea el uso std::is_integral?

NKAR
fuente
1
Un ejemplo es que se vuelve mucho más fácil implementar constructores condicionalmente explícitos como los que se usan tuplecon esta característica.
Pretoriano
1
No es una respuesta adecuada, pero también puede ver la motivación en el documento que la presentó: wg21.link/p0892
N. Shead
Ejemplo: (junto con los conceptos) reduce el número requerido de clases base para implementar un constructor de copias condicionalmente explícito provisto condicionalmente de 3 a 0.
LF

Respuestas:

21

La motivación en sí misma se puede ver en el documento .

Es necesario hacer que los constructores sean condicionalmente explícitos. Es decir, quieres:

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

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

Lo primero está bien, esos constructores están implícitos. Pero esto último sería malo, esos constructores lo son explicit. Con C ++ 17 (o C ++ 20 con conceptos), la única forma de hacer que esto funcione es escribir dos constructores, uno explicity otro no:

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

Estos están casi completamente duplicados, y las definiciones de estos constructores serían idénticas.

Con explicit(bool), puede escribir un solo constructor, con la parte condicionalmente explícita de la construcción localizada solo en el explicitespecificador:

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

Esto coincide mejor con la intención, es mucho menos código para escribir y es menos trabajo para el compilador durante la resolución de sobrecarga (ya que hay menos constructores para elegir).

Barry
fuente
1
C ++ 20 también proporciona la capacidad de cambiar la enable_if_tparte a una restricción más bonita y simple, posiblemente utilizando conceptos. Pero eso no viene al caso en esta pregunta.
Aschepler
2

Otro posible uso que veo es con una plantilla variadic:

Generalmente es bueno, por defecto, tener explicitun constructor con un solo argumento (a menos que se desee la conversión).

entonces

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

    // ...
};
Jarod42
fuente
0

Pude ver un caso de uso para requerir explicitcondicionalmente cuando la entrada podría ser un tipo de vista (puntero sin formato std::string_view) al que el nuevo objeto se aferrará después de la llamada (solo copiando la vista, no a lo que se refiere, dependiendo de la vida útil del objeto visto), o podría ser un tipo de valor (toma posesión de una copia, sin dependencias externas de por vida).

En una situación como esa, la persona que llama es responsable de mantener vivo el objeto visto (la persona que llama posee una vista, no el objeto original), y la conversión no debe hacerse implícitamente, porque hace que sea demasiado fácil para el objeto creado implícitamente Sobrevive al objeto que ve. Por el contrario, para los tipos de valor, el nuevo objeto recibirá su propia copia, por lo que si bien la copia puede ser costosa, no hará que el código sea incorrecto si se produce una conversión implícita.

ShadowRanger
fuente