¿Por qué las funciones eliminadas de C ++ 11 participan en la resolución de sobrecargas?


Respuestas:


114

La mitad del propósito de la = deletesintaxis es poder evitar que las personas llamen a ciertas funciones con ciertos parámetros. Esto es principalmente para evitar conversiones implícitas en ciertos escenarios específicos. Para prohibir una sobrecarga en particular, debe participar en la resolución de la sobrecarga.

La respuesta que cita le da un ejemplo perfecto:

struct onlydouble {
  onlydouble(std::intmax_t) = delete;
  onlydouble(double);
};

Si se deleteelimina la función por completo, la = deletesintaxis sería equivalente a esto:

struct onlydouble2 {
  onlydouble2(double);
};

Podrías hacer esto:

onlydouble2 val(20);

Esto es C ++ legal. El compilador examinará todos los constructores; ninguno de ellos toma directamente un tipo entero. Pero uno de ellos puede tomarlo después de una conversión implícita. Entonces lo llamará así.

onlydouble val(20);

Esto no es C ++ legal. El compilador examinará todos los constructores, incluidos los deleted. Verá una coincidencia exacta, vía std::intmax_t(que coincidirá exactamente con cualquier literal entero). Entonces, el compilador lo seleccionará y luego emitirá un error inmediatamente, porque seleccionó una deletefunción d.

= deletesignifica "prohíbo esto", no simplemente "esto no existe". Es una declaración mucho más fuerte.

Estaba preguntando por qué el estándar C ++ dice = eliminar significa "Prohibo esto" en lugar de "esto no existe"

Es porque no necesitamos una gramática especial para decir "esto no existe". Obtenemos esto implícitamente simplemente no declarando el "esto" particular en cuestión. "Prohibo esto" representa una construcción que no se puede lograr sin una gramática especial. Entonces obtenemos una gramática especial para decir "prohíbo esto" y no lo otro.

La única funcionalidad que obtendría al tener una gramática explícita de "esto no existe" sería evitar que alguien más tarde declare que existe. Y eso no es lo suficientemente útil como para necesitar su propia gramática.

De lo contrario, no hay forma de declarar que el constructor de copia no existe, y su existencia puede causar ambigüedades sin sentido.

El constructor de copia es una función miembro especial. Cada clase siempre tiene un constructor de copias. Del mismo modo que siempre tienen un operador de asignación de copia, un constructor de movimiento, etc.

Estas funciones existen; la pregunta es solo si es legal llamarlos. Si intenta decir que eso = deletesignifica que no existen, entonces la especificación tendría que explicar qué significa que una función no exista. Este no es un concepto que maneja la especificación.

Si intenta llamar a una función que aún no ha sido declarada / definida, entonces el compilador producirá un error. Pero se producirá un error debido a un identificador indefinido , no debido a un error de "la función no existe" (incluso si su compilador lo informa de esa manera). Todos los constructores son llamados por resolución de sobrecarga, por lo que su "existencia" se maneja en ese sentido.

En todos los casos, hay una función declarada a través de un identificador, o un constructor / destructor (también declarado a través de un identificador, solo un identificador de tipo). La sobrecarga del operador oculta el identificador detrás del azúcar sintáctico, pero sigue ahí.

La especificación C ++ no puede manejar el concepto de una "función que no existe". Puede manejar un desajuste de sobrecarga. Puede manejar una ambigüedad de sobrecarga. Pero no sabe lo que no está. Así que = deletese define en términos de los "intentos de llamar a esto fallidos" mucho más útiles que los menos útiles "pretenden que nunca escribí esta línea".

Y nuevamente, vuelva a leer la primera parte. No puede hacer eso con "la función no existe". Esa es otra razón por la que se define de esa manera: porque uno de los principales casos de uso de la = deletesintaxis es poder forzar al usuario a usar ciertos tipos de parámetros, a emitir explícitamente, etc. Básicamente, para frustrar las conversiones de tipos implícitas.

Tu sugerencia no haría eso.


1
@Mehrdad: Si necesita más aclaraciones sobre por qué = delete no funciona de la manera que desea, debe ser más claro sobre la semántica exacta que cree que = delete debería tener. ¿Debería ser "fingir que nunca escribí esta línea"? ¿O debería ser otra cosa?
Nicol Bolas

1
No, quiero decir que esperaba = deletesignificar "este miembro no existe", lo que implicaría que no podría participar en la resolución de sobrecarga.
user541686

6
@Mehrdad: Y eso se remonta a mi punto original, por lo que lo publiqué: si = deletesignifica "este miembro no existe", entonces el primer ejemplo que publiqué no podría evitar que las personas pasen enteros al onlydoubleconstructor de , ya que la onlydoublesobrecarga que se elimina no existiría . No participaría en la resolución de sobrecarga y, por lo tanto, no le impediría pasar enteros. Que es la mitad del punto de la = deletesintaxis: poder decir, "No se puede pasar X implícitamente a esta función".
Nicol Bolas

3
@Mehrdad: Por esa lógica, ¿por qué lo necesitas =delete? Después de todo, podemos decir "no copiable" haciendo exactamente lo mismo: declarar el constructor de copia / asignación privada. Además, tenga en cuenta que declarar algo privado no lo convierte en incalculable; el código dentro de la clase todavía puede llamarlo. Entonces no es lo mismo que = delete. No, la = deletesintaxis nos permite hacer algo que antes era muy inconveniente e inescrutable de una manera mucho más obvia y razonable.
Nicol Bolas

2
@Mehrdad: Porque esto último es posible : se llama "no lo declares". Entonces no existirá. En cuanto a por qué necesitamos sintaxis para ocultar una sobrecarga en lugar de abusar de lo privado ... ¿realmente se pregunta por qué deberíamos tener un medio para declarar algo explícitamente, en lugar de abusar de alguna otra característica para obtener el mismo efecto ? Para cualquiera que lea el código, es más obvio lo que está sucediendo. Hace que el código sea más comprensible para el usuario y facilita la escritura, al mismo tiempo que soluciona problemas en la solución. Tampoco necesitamos lambdas.
Nicol Bolas

10

El Borrador de trabajo de C ++ 2012-11-02 no proporciona una justificación detrás de esta regla, solo algunos ejemplos

8.4.3 Definiciones eliminadas [dcl.fct.def.delete]
...
3 [ Ejemplo : Se puede hacer cumplir la inicialización no predeterminada y la inicialización no integral con

struct onlydouble {  
  onlydouble() = delete; // OK, but redundant  
  onlydouble(std::intmax_t) = delete;  
  onlydouble(double);  
};  

- end example ]
[ Ejemplo : Se puede evitar el uso de una clase en ciertas expresiones nuevas usando definiciones eliminadas de un operador nuevo declarado por el usuario para esa clase.

struct sometype {  
  void *operator new(std::size_t) = delete;  
  void *operator new[](std::size_t) = delete;  
};  
sometype *p = new sometype; // error, deleted class operator new  
sometype *q = new sometype[3]; // error, deleted class operator new[]  

- end example ]
[ Ejemplo : Se puede hacer que una clase no se pueda copiar, es decir, solo mover, utilizando definiciones eliminadas del constructor de copia y el operador de asignación de copia, y luego proporcionando definiciones predeterminadas del constructor de movimiento y el operador de asignación de movimiento.

struct moveonly {  
  moveonly() = default;  
  moveonly(const moveonly&) = delete;  
  moveonly(moveonly&&) = default;  
  moveonly& operator=(const moveonly&) = delete;  
  moveonly& operator=(moveonly&&) = default;  
  ~moveonly() = default;  
};  
moveonly *p;  
moveonly q(*p); // error, deleted copy constructor  

- ejemplo final ]


4
La justificación parece muy clara a partir de los ejemplos, ¿no crees?
Jesse Good
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.