(¿Por qué) son std::chrono literales lvalues?

4 minutos de lectura

Básicamente, el ejemplo lo dice todo: asignar a un literal std::chrono – no da un mensaje de error. Probé en gcc, clang y vc, (c++17) todos estos compiladores lo aceptan. ¿Es este el comportamiento previsto?

#include <chrono>
using namespace std::chrono_literals;

int main()
{
    10ms  += 1ms;       // would expect error here
    12ms = 10ms;        // and here
    // 3 = 4;           // like here
    return 0;
}

  • Aquí hay un ilustración de lo que pasa

    – Ted Lyngmo

    28 de agosto de 2020 a las 11:27


  • Buena pregunta hecha … Solo estoy votando 🙂

    – Satish Hawalppagol

    1 de septiembre de 2020 a las 18:08

avatar de usuario
erorika

son std::chrono literales lvalues?

No. std::chrono_literals son prvalores.

¿Es este el comportamiento previsto?

Es como lo especifica la norma.

Estos valores son de tipos de clase. Y se pueden asignar valores de tipos de clase1 siempre que la clase sea asignable y la sobrecarga de su operador de asignación no esté calificada como lvalue ref. Los operadores de asignación de clases, como todas las funciones miembro, no están calificados como lvalue ref por defecto, y casi ninguna clase estándar tiene tal sobrecarga calificada2 – No conozco ninguna pero si las hay, std::chrono::duration no es uno de ellos.

Tenga en cuenta que el literal no se modifica. El operando de la izquierda es un objeto temporal que en este caso se descarta.


Otro ejemplo simple de asignación de un valor r:

std::string{"temporary"} = "this is pointless, but legal";

1 En realidad, esta es una característica útil, aunque no hay muchos casos de uso. Aquí hay un ejemplo útil:

std::vector<bool> vec{true, false, true};
vec[1] = true; // vec[1] is an rvalue

2 Antes de C ++ 11, no había calificadores de referencia y, por lo tanto, se podían asignar todos los valores r de los tipos de clase asignables. Para mantener la compatibilidad con versiones anteriores, no se puede cambiar el valor predeterminado ni las clases preexistentes (al menos no sin un período de obsolescencia). std::chrono::duration se agregó en C++ 11, por lo que podría haber tenido un operador de asignación calificado, pero los autores estándar parecen preferir no calificar sus operadores de asignación.

  • Simplemente no es cierto que “los literales… son prvalues”. Los literales de cadena son valores l, y no hay nada que le impida escribir un operador literal definido por el usuario que devuelva un valor l.

    – Brian Bi

    28 de agosto de 2020 a las 19:40

  • @Brian modifiqué la respuesta. ¿Estarías de acuerdo en que ahora sea correcto?

    – erorika

    28 de agosto de 2020 a las 19:44

  • En cuanto a si está “prevista”, no diría que esta característica específicamente para estos tipos está “prevista”, aunque es una consecuencia de reglas más generales que lo son. ¿Es “deseable”? No, en realidad no, y esto es lo que sucede cuando introduces con calzador funciones de lenguaje en la biblioteca como si fueran código de usuario.

    – Asteroides Con Alas

    29 de agosto de 2020 a las 15:19

avatar de usuario
dfrib

los std::chrono los literales no son lvalues. Pueden aparecer como tales, pero lo que está viendo es en realidad la invocación de operadores de asignación por valores r. ¿Por qué se permite esto? Específicamente, porque, por ejemplo, el operador de asignación de miembros

constexpr duration& operator+=(const duration& d)

como se especifica por [time.duration.arithmetic]/9no utiliza ningún calificador de referencia, lo que significa que no rechaza ser invocado por valores r.

Compare con el siguiente ejemplo (implementado como duration arriba)

struct Foo {
    unsigned long long i;
    constexpr Foo& operator+=(const Foo& d) { return *this; }
};

constexpr Foo operator"" _Foo (unsigned long long i) {
    return Foo{i};
}

int main() {
    1_Foo += 2_Foo;  // OK!
}

en comparación con el siguiente ejemplo, que explícitamente hace uso de un & ref-calificador para prohibir implícitamente la invocación en valores r:

struct Foo {
    unsigned long long i;
    constexpr Foo& operator+=(const Foo& d) & { return *this; }
};

constexpr Foo operator"" _Foo (unsigned long long i) {
    return Foo{i};
}

int main() {
    1_Foo += 2_Foo;  // error: no viable overloaded '+='
}

o, el siguiente ejemplo, que explícitamente elimina la sobrecarga del valor r:

struct Foo {
    unsigned long long i;
    constexpr Foo& operator+=(const Foo& d) & { return *this; }
    constexpr Foo& operator+=(const Foo& d) && = delete;
};

constexpr Foo operator"" _Foo (unsigned long long i) {
    return Foo{i};
}

int main() {
    1_Foo += 2_Foo;  // error: overload resolution selected deleted operator '+='
}

Para obtener detalles sobre los calificadores de valor r y cómo podría tener sentido aplicarlos para prohibir la asignación de valor r, consulte, por ejemplo, las siguientes preguntas y respuestas:

  • ¿Qué significa el & (ampersand) al final de la firma de la función miembro?

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad