El extraño problema del lenguaje es CWG 1581 :
La cláusula 15 [especial] es perfectamente clara de que las funciones miembro especiales solo se definen implícitamente cuando se usan odr. Esto crea un problema para las expresiones constantes en contextos no evaluados:
struct duration {
constexpr duration() {}
constexpr operator int() const { return 0; }
};
// duration d = duration(); // #1
int n = sizeof(short{duration(duration())});
El problema aquí es que no se nos permite definir implícitamente constexpr duration::duration(duration&&)
en este programa, por lo que la expresión en la lista de inicializadores no es una expresión constante (porque invoca una función constexpr que no se ha definido), por lo que el inicializador entre paréntesis contiene una conversión de reducción , por lo que el programa está mal formado.
Si descomentamos la línea # 1, el constructor de movimiento está implícitamente definido y el programa es válido. Esta acción espeluznante a distancia es extremadamente desafortunada. Las implementaciones divergen en este punto.
Puede leer el resto de la descripción del problema.
Se adoptó una resolución para este problema en P0859 en Albuquerque en 2017 (después del envío de C ++ 17). Ese problema fue un bloqueador para poder tener un constexpr std::swap
(resuelto en P0879 ) y un constexpr std::invoke
(resuelto en P1065 , que también tiene ejemplos CWG1581), ambos para C ++ 20.
El ejemplo más simple de entender aquí, en mi opinión, es el código del informe de error LLVM señalado en P1065:
template<typename T>
int f(T x)
{
return x.get();
}
template<typename T>
constexpr int g(T x)
{
return x.get();
}
int main() {
// O.K. The body of `f' is not required.
decltype(f(0)) a;
// Seems to instantiate the body of `g'
// and results in an error.
decltype(g(0)) b;
return 0;
}
CWG1581 se trata de cuándo se definen las funciones de miembro constexpr, y la resolución garantiza que solo se definan cuando se usan. Después de P0859, lo anterior está bien formado (el tipo de b
es int
).
Dado std::swap
y std::invoke
ambos tienen que confiar en la comprobación de las funciones miembro (mover la construcción / asignación en el primero y el operador de llamada / llamadas sustitutas en el segundo), ambos dependían de la resolución de este problema.
std::is_move_constructible_v<T> && std::is_move_assignable_v<T>
estrue
. Eso no puede suceder si las funciones especiales del miembro aún no se generan.La razón
(debido a @NathanOliver)
Para permitir una
constexpr
función de intercambio, debe verificar, antes de crear una instancia de la plantilla para esta función, que el tipo intercambiado sea move-constructible y move-asignable. Desafortunadamente, debido a un defecto del lenguaje que solo se resolvió en C ++ 20, no puede verificarlo, ya que las funciones miembro relevantes pueden no haberse definido aún, en lo que respecta al compilador.La cronologia
<algorithm>
funciones comoconstexpr
.constexpr std::swap()
y tambiénconstexpr std::invoke()
, vea la explicación anterior.std::swap
y algunas otras construcciones, y esto se acepta en C ++ 17.std::swap()
constexpr después de la resolución de CWG-1581.std::invoke()
solución constexpr .Su caso especifico
Puede utilizar el
constexpr
intercambio si no verifica la capacidad de construcción y la capacidad de asignación de movimiento, sino que verifica directamente alguna otra característica de tipos que lo garantice en particular. por ejemplo, solo tipos primitivos y sin clases ni estructuras. O, en teoría, podría renunciar a las comprobaciones y simplemente lidiar con los errores de compilación que pueda encontrar, y con el cambio de comportamiento débil entre los compiladores. En cualquier caso, no reemplacestd::swap()
con ese tipo de cosas.fuente