qual é o caso de uso explícito (bool)

24

O C ++ 20 introduziu explícito (bool) que seleciona condicionalmente em tempo de compilação se um construtor é explicitado ou não.

Abaixo está um exemplo que eu encontrei aqui .

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

Alguém pode me dizer outro caso explicit (bool)de uso que não seja o uso std::is_integral?

NKAR
fonte
11
Um exemplo é que torna-se muito mais fácil implementar construtores condicionalmente explícitos como aqueles tuplecom esse recurso.
Pretoriano
11
Não é uma resposta adequada, mas você também pode observar a motivação no documento que a apresentou: wg21.link/p0892
N. Shead
Exemplo: (junto com os conceitos) reduz o número necessário de classes base para implementar um construtor de cópias condicionalmente explícito, fornecido de forma condicional, de 3 para 0.
LF

Respostas:

21

A motivação em si pode ser vista no artigo .

É necessário tornar os construtores condicionalmente explícitos. Ou seja, você quer:

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

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

O primeiro está bom, esses construtores estão implícitos. Mas o último seria ruim, esses construtores são explicit. Com o C ++ 17 (ou C ++ 20 com conceitos), a única maneira de fazer esse trabalho é escrever dois construtores - um explicite um não:

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

Estes são quase inteiramente duplicados - e as definições desses construtores seriam idênticas.

Com explicit(bool), você pode simplesmente escrever um único construtor - com a parte condicionalmente explícita da construção localizada apenas no 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&& );   
};

Isso combina melhor com a intenção, é muito menos código para escrever e é menos trabalhoso para o compilador durante a resolução de sobrecarga (já que há menos construtores para escolher).

Barry
fonte
11
O C ++ 20 também oferece a capacidade de alterar a enable_if_tpeça para uma restrição mais bonita e mais simples, possivelmente usando conceitos. Mas isso não vem ao caso desta questão.
aschepler
2

Outro uso possível que vejo é com o modelo variadic:

Geralmente é bom, por padrão, ter explicitum construtor com apenas um argumento (a menos que a conversão seja desejada).

tão

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

    // ...
};
Jarod42
fonte
0

Eu pude ver um caso de uso para exigir explicitcondicionalmente quando a entrada pode ser do tipo de exibição (ponteiro bruto std::string_view) , que o novo objeto manterá após a chamada (apenas copiando a exibição, não o que ela se refere, permanecendo dependente de a vida útil do objeto visualizado) ou pode ser do tipo valor (assume a propriedade de uma cópia, sem dependências externas da vida útil).

Em uma situação como essa, o chamador é responsável por manter o objeto visualizado vivo (o receptor possui uma visualização, não o objeto original), e a conversão não deve ser feita implicitamente, porque facilita demais o objeto criado implicitamente. sobreviver ao objeto que vê. Por outro lado, para tipos de valor, o novo objeto receberá sua própria cópia; portanto, embora a cópia possa ser cara, ela não cometerá o código errado se ocorrer uma conversão implícita.

ShadowRanger
fonte