¿Por qué std :: swap no está marcado como constexpr antes de C ++ 20?

14

En C ++ 20, se std::swapconvierte en una constexprfunción.

Sé que la biblioteca estándar realmente se quedó atrás del lenguaje al marcar cosas constexpr, pero para 2017, <algorithm>era bastante importante, al igual que muchas otras cosas. Sin embargo, std::swapno lo fue. Recuerdo vagamente que hay algún defecto extraño en el lenguaje que impide esa marca, pero olvido los detalles.

¿Alguien puede explicar esto de manera sucinta y clara?

Motivación: es necesario comprender por qué puede ser una mala idea marcar una std::swap()función similar constexpren el código C ++ 11 / C ++ 14.

einpoklum
fuente

Respuestas:

11

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 bes int).

Dado std::swapy std::invokeambos 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.

Barry
fuente
Entonces, ¿por qué CWG-1581 previene / hace que sea indeseable marcar una función de intercambio como constexpr?
Einpoklum
3
El intercambio de @einpoklum requiere std::is_move_constructible_v<T> && std::is_move_assignable_v<T>es true. Eso no puede suceder si las funciones especiales del miembro aún no se generan.
NathanOliver
@NathanOliver: Agregué esto a mi respuesta.
Einpoklum
5

La razón

(debido a @NathanOliver)

Para permitir una constexprfunció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

  • 2016: Antony Polukhin presenta la propuesta P0202 , para marcar todas las <algorithm>funciones como constexpr.
  • El grupo de trabajo central del comité estándar discute el defecto CWG-1581 . Este problema hizo que fuera problemático tenerlo constexpr std::swap()y también constexpr std::invoke(), vea la explicación anterior.
  • 2017: Antony revisa su propuesta varias veces para excluir std::swapy algunas otras construcciones, y esto se acepta en C ++ 17.
  • 2017: Se presenta una resolución para el problema CWG -1581 como P0859 y es aceptada por el comité estándar en 2017 (pero después del envío de C ++ 17).
  • Finales de 2017: Antony presenta una propuesta complementaria, P0879 , para elaborar std::swap()constexpr después de la resolución de CWG-1581.
  • 2018: la propuesta complementaria se acepta (?) En C ++ 20. Como señala Barry, también lo es la std::invoke()solución constexpr .

Su caso especifico

Puede utilizar el constexprintercambio 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 reemplace std::swap()con ese tipo de cosas.

einpoklum
fuente