Evite que la función tome const std :: string & de aceptar 0

97

Vale más que mil palabras:

#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;
}

El compilador no se queja al pasar el número 0 al operador de soporte que acepta una cadena. En cambio, esto se compila y falla antes de ingresar al método con:

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

Para referencia:

> 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)

Mi conjetura

El compilador está usando implícitamente el std::string(0)constructor para ingresar el método, lo que genera el mismo problema (google el error anterior) sin ninguna buena razón.

Pregunta

¿Hay alguna forma de arreglar esto en el lado de la clase, para que el usuario de la API no sienta esto y el error se detecte en el momento de la compilación?

Es decir, agregar una sobrecarga

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

No es una buena solución.

kabanus
fuente
2
Código compilado, lanzando una excepción en Visual Studio en ohNo [0] con la excepción "0xC0000005: Ubicación de lectura de infracción de acceso 0x00000000"
TruthSeeker
55
Declare una sobrecarga privada operator[]()que acepte un intargumento y no lo defina.
Peter
2
@ Peter Aunque tenga en cuenta que es un error de enlace , que aún es mejor que lo que tenía.
kabanus
55
@kabanus En el escenario anterior, será un error del compilador , ¡porque el operador es privado! Error del enlazador solo si se llama dentro de la clase ...
Aconcagua
55
@Peter Eso es especialmente interesante en escenarios donde no C ++ 11 está disponible - y estos qué existen aún hoy en día (en realidad estoy en un proyecto de tener que tratar, y que me falta más o menos algunas de las nuevas características ... )
Aconcagua

Respuestas:

161

El motivo std::string(0)es válido, se debe a que 0es un puntero nulo constante. Entonces 0 coincide con el constructor de cadenas que toma un puntero. Luego, el código entra en conflicto con la condición previa a la que no se puede pasar un puntero nulo std::string.

Solo el literal 0se interpretaría como una constante de puntero nulo, si fuera un valor de tiempo de ejecución en un intno tendría este problema (porque entonces la resolución de sobrecarga buscaría una intconversión). Tampoco es literal 1un problema, porque 1no es un puntero nulo constante.

Dado que es un problema de tiempo de compilación (valores no válidos literales), puede detectarlo en tiempo de compilación. Agregue una sobrecarga de este formulario:

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

std::nullptr_tes el tipo de nullptr. Y que coincidirá con cualquier constante puntero nulo, ya sea 0, 0ULLo nullptr. Y dado que la función se elimina, provocará un error de tiempo de compilación durante la resolución de sobrecarga.

StoryTeller - Unslander Monica
fuente
Esta es, de lejos, la mejor solución, olvidé por completo que puedo sobrecargar un puntero NULL.
kabanus
en Visual Studio, incluso "ohNo [0]" arroja una excepción de valor nulo. ¿Significa una implementación específica de la clase std :: string?
TruthSeeker
@pmp Lo que se lanza (si corresponde) es específico de la implementación, pero el punto es que la cadena es un puntero NULO en todos ellos. Con esta solución no llegará a la parte de excepción, se detectará en el momento de la compilación.
kabanus
18
@pmp: std::stringel estándar C ++ no permite pasar un puntero nulo al constructor. Es un comportamiento indefinido, por lo que MSVC puede hacer lo que quiera (como lanzar una excepción).
StoryTeller - Unslander Monica
26

Una opción es declarar una privatesobrecarga operator[]()que acepte un argumento integral y no lo defina.

Esta opción funcionará con todos los estándares de C ++ (1998 en adelante), a diferencia de opciones como las void operator[](std::nullptr_t) = deleteque son válidas desde C ++ 11.

Hacer que operator[]()un privatemiembro provoque un error diagnosticable en su ejemplo ohNo[0], a menos que esa expresión sea utilizada por una función miembro o friendde la clase.

Si esa expresión se usa desde una función miembro o friendde la clase, el código se compilará pero, dado que la función no está definida, generalmente la compilación fallará (por ejemplo, un error de enlace debido a una función indefinida).

Peter
fuente