Impedir que a função pegue const std :: string e aceite 0

97

Vale mais que mil palavras:

#include<string>
#include<iostream>

class SayWhat {
    public:
    SayWhat& operator[](const std::string& s) {
        std::cout<<"here\n"; // To make sure we fail on function entry
        std::cout<<s<<"\n";
        return *this;
    }
};

int main() {
    SayWhat ohNo;
    // ohNo[1]; // Does not compile. Logic prevails.
    ohNo[0]; // you didn't! this compiles.
    return 0;
}

O compilador não está reclamando ao passar o número 0 para o operador de colchete que aceita uma string. Em vez disso, isso compila e falha antes da entrada no método com:

terminate called after throwing an instance of 'std::logic_error'
  what():  basic_string::_S_construct null not valid

Para referência:

> g++ -std=c++17 -O3 -Wall -Werror -pedantic test.cpp -o test && ./test
> g++ --version
gcc version 7.3.1 20180303 (Red Hat 7.3.1-5) (GCC)

Meu palpite

O compilador está implicitamente usando o std::string(0)construtor para inserir o método, o que gera o mesmo problema (pesquise no google o erro acima) sem uma boa razão.

Questão

Existe alguma maneira de corrigir isso no lado da classe, para que o usuário da API não sinta isso e o erro seja detectado no momento da compilação?

Ou seja, adicionando uma sobrecarga

void operator[](size_t t) {
    throw std::runtime_error("don't");
}

não é uma boa solução.

kabanus
fonte
2
Código compilado, lançando exceção no Visual studio em ohNo [0] com exceção "0xC0000005: local de leitura de violação de acesso 0x00000000"
TruthSeeker
5
Declare que uma sobrecarga privada operator[]()aceita um intargumento e não o defina.
Peter
2
@ Peter Embora note que é um erro de vinculador , que ainda é melhor do que o que eu tinha.
Kabanus
5
@kabanus No cenário acima, haverá um erro do compilador , porque o operador é privado! Erro de vinculador apenas se chamado dentro classe ...
Aconcagua
5
@ Peter Isso é especialmente interessante em cenários em que não há C ++ 11 disponível - e eles ainda existem hoje (na verdade, eu estou em um projeto que precisa lidar com isso e sinto falta de alguns dos novos recursos ... )
Aconcagua

Respostas:

161

O motivo std::string(0)é válido, devido a 0ser uma constante de ponteiro nulo. Portanto, 0 corresponde ao construtor de cadeias usando um ponteiro. Em seguida, o código entra em conflito com a pré-condição para a qual não se pode passar um ponteiro nulo std::string.

Somente literal 0seria interpretado como uma constante de ponteiro nulo, se fosse um valor de tempo de execução em um caso, intvocê não teria esse problema (porque a resolução de sobrecarga estaria procurando uma intconversão). Nem literalmente é 1um problema, porque 1não é uma constante de ponteiro nulo.

Como é um problema de tempo de compilação (valores inválidos literais), você pode detectá-lo no momento da compilação. Adicione uma sobrecarga deste formulário:

void operator[](std::nullptr_t) = delete;

std::nullptr_té do tipo de nullptr. E ele irá corresponder qualquer constante ponteiro nulo, seja ele 0, 0ULLou nullptr. E, como a função é excluída, isso causará um erro de tempo de compilação durante a resolução de sobrecarga.

Contador de Histórias - Monica Sem Calúnia
fonte
Esta é de longe a melhor solução, esqueci completamente que posso sobrecarregar um ponteiro NULL.
Kabanus
no Visual Studio, até "ohNo [0]" lança uma exceção de valor nulo. Isso significa uma implementação específica da classe std :: string?
TruthSeeker 18/11/19
@pmp O que é lançado (se houver) é específico da implementação, mas o ponto é que a string é um ponteiro NULL em todos eles. Com esta solução, você não chegará à parte da exceção, ela será detectada no momento da compilação.
Kabanus
18
@pmp - Passar um ponteiro nulo para std::stringo construtor não é permitido pelo padrão C ++. É um comportamento indefinido, para que o MSVC possa fazer o que quiser (como lançar uma exceção).
StoryTeller - Unslander Monica 18/11/19
26

Uma opção é declarar que uma privatesobrecarga operator[]()aceita um argumento integral e não o define.

Esta opção funcionará com todos os padrões C ++ (1998 em diante), ao contrário de opções como as void operator[](std::nullptr_t) = deleteque são válidas a partir do C ++ 11.

Tornar operator[]()um privatemembro causará um erro diagnosticável no seu exemplo ohNo[0], a menos que essa expressão seja usada por uma função de membro ou friendda classe.

Se essa expressão for usada em uma função membro ou friendda classe, o código será compilado, mas - como a função não está definida - geralmente a compilação falhará (por exemplo, um erro do vinculador devido a uma função indefinida).

Pedro
fonte