Pregunta

C ++ 11 introduce literales definidos por el usuario que permitirá la introducción de una nueva sintaxis literal basada en los existentes literales ( int , hex , cadena , float ) para que cualquier tipo pueda tener una presentación literal .

Ejemplos :

// imaginary numbers
std::complex<long double> operator "" _i(long double d) // cooked form
{ 
    return std::complex<long double>(0, d); 
}
auto val = 3.14_i; // val = complex<long double>(0, 3.14)

// binary values
int operator "" _B(const char*); // raw form
int answer = 101010_B; // answer = 42

// std::string
std::string operator "" _s(const char* str, size_t /*length*/) 
{ 
    return std::string(str); 
}

auto hi = "hello"_s + " world"; // + works, "hello"_s is a string not a pointer

// units
assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

A primera vista, esto se ve muy bien, pero me pregunto qué tan aplicable es, cuando intenté pensar en tener los sufijos _AD y _BC crear fechas I encontró que es problemático debido al pedido del operador. 1974/01 / 06_AD evaluaría primero 1974/01 (como llano int s) y solo posteriormente 06_AD (por no decir nada de agosto y septiembre por escrito sin el 0 por razones octales). Se puede solucionar esto haciendo que la sintaxis sea 1974-1 / 6_AD_code> de modo que la orden de evaluación del operador funcione, pero es poco práctica.

Así que mi pregunta se reduce a esto: ¿sientes que esta característica se justificará? ¿Qué otros literales te gustaría definir que hagan que tu código C ++ sea más legible?


Sintaxis actualizada para ajustarse al borrador final en junio de 2011

¿Fue útil?

Solución

Este es un caso en el que hay una ventaja de usar literales definidos por el usuario en lugar de una llamada de constructor:

#include <bitset>
#include <iostream>

template<char... Bits>
  struct checkbits
  {
    static const bool valid = false;
  };

template<char High, char... Bits>
  struct checkbits<High, Bits...>
  {
    static const bool valid = (High == '0' || High == '1')
                   && checkbits<Bits...>::valid;
  };

template<char High>
  struct checkbits<High>
  {
    static const bool valid = (High == '0' || High == '1');
  };

template<char... Bits>
  inline constexpr std::bitset<sizeof...(Bits)>
  operator"" _bits() noexcept
  {
    static_assert(checkbits<Bits...>::valid, "invalid digit in binary string");
    return std::bitset<sizeof...(Bits)>((char []){Bits..., '\0'});
  }

int
main()
{
  auto bits = 0101010101010101010101010101010101010101010101010101010101010101_bits;
  std::cout << bits << std::endl;
  std::cout << "size = " << bits.size() << std::endl;
  std::cout << "count = " << bits.count() << std::endl;
  std::cout << "value = " << bits.to_ullong() << std::endl;

  //  This triggers the static_assert at compile time.
  auto badbits = 2101010101010101010101010101010101010101010101010101010101010101_bits;

  //  This throws at run time.
  std::bitset<64> badbits2("2101010101010101010101010101010101010101010101010101010101010101_bits");
}

La ventaja es que una excepción de tiempo de ejecución se convierte en un error de tiempo de compilación. No se pudo agregar la aserción estática al bit de conjunto de bits que toma una cadena (al menos no sin argumentos de plantilla de cadena).

Otros consejos

A primera vista, parece ser un simple azúcar sintáctico.

Pero cuando miramos más a fondo, vemos que es más que azúcar sintáctico, ya que amplía las opciones del usuario de C ++ para crear tipos definidos por el usuario que se comportan exactamente como distintos tipos incorporados. En esto, esto pequeño " bonificación " es una adición muy interesante de C ++ 11 a C ++.

¿Realmente lo necesitamos en C ++?

Veo pocos usos en el código que escribí en los últimos años, pero solo porque no lo usé en C ++ no significa que no sea interesante para otro desarrollador de C ++ .

Habíamos utilizado en C ++ (y en C, supongo), literales definidos por el compilador, para escribir números enteros como enteros cortos o largos, números reales como flotantes o dobles (o incluso dobles), y cadenas de caracteres como normales o caracteres anchos.

En C ++, tuvimos la posibilidad de crear nuestros propios tipos (es decir, clases), potencialmente sin sobrecarga (en línea, etc.). Tuvimos la posibilidad de agregar operadores a sus tipos, hacer que se comportaran como tipos incorporados similares, lo que permite a los desarrolladores de C ++ usar matrices y números complejos de la forma más natural que lo harían si se hubieran agregado al lenguaje en sí. Incluso podemos agregar operadores de cast (lo que suele ser una mala idea, pero a veces es la solución correcta).

Todavía faltaba una cosa para que los tipos de usuarios se comportaran como tipos integrados: literales definidos por el usuario.

Entonces, supongo que es una evolución natural para el lenguaje, pero para ser lo más completo posible: " Si desea crear un tipo, y desea que se comporte lo más posible como una función integrada. tipos, aquí están las herramientas ... "

Supongo que es muy similar a la decisión de .NET de hacer que cada estructura primitiva, incluidos los booleanos, los enteros, etc., y todas las estructuras se deriven de Object. Solo con esta decisión, .NET está mucho más allá del alcance de Java cuando se trabaja con primitivos, independientemente de la cantidad de hacks de boxeo / desempaquetado que Java agregará a su especificación.

¿Realmente lo necesitas en C ++?

Esta pregunta es para que USTED responda. No Bjarne Stroustrup. No Herb Sutter. No cualquier miembro del comité estándar de C ++. Esta es la razón por la que tiene la opción en C ++ , y no restringirán una notación útil solo a los tipos incorporados.

Si usted lo necesita, entonces es una adición bienvenida. Si usted no lo hace, bueno ... No lo use. No te costará nada.

Bienvenido a C ++, el idioma donde las funciones son opcionales.

Bloqueado ??? ¡Muéstrame tus complejos!

Hay una diferencia entre hinchado y complejo (juego de palabras intencionado).

Como se muestra por Niels en ¿Qué nuevas capacidades agregan los literales definidos por el usuario a C ++? , poder escribir un número complejo es una de las dos características agregadas " recientemente " a C y C ++:

// C89:
MyComplex z1 = { 1, 2 } ;

// C99: You'll note I is a macro, which can lead
// to very interesting situations...
double complex z1 = 1 + 2*I;

// C++:
std::complex<double> z1(1, 2) ;

// C++11: You'll note that "i" won't ever bother
// you elsewhere
std::complex<double> z1 = 1 + 2_i ;

Ahora, ambos C99 "complejo doble" " tipo y C ++ " std :: complex " los tipos se pueden multiplicar, sumar, restar, etc., mediante la sobrecarga del operador.

Pero en C99, acaban de agregar otro tipo como tipo integrado y soporte de sobrecarga de operador incorporado. Y agregaron otra característica literal incorporada.

En C ++, solo usaron las características existentes del lenguaje, vieron que la característica literal era una evolución natural del lenguaje y, por lo tanto, lo agregaron.

En C, si necesita la misma mejora de notación para otro tipo, no tendrá suerte hasta que haga cabildeo para agregar sus funciones de onda cuántica (o puntos 3D, o cualquier tipo básico que esté usando en su campo de trabajo). ) para el estándar C como un tipo incorporado tiene éxito.

En C ++ 11, puedes hacerlo tú mismo:

Point p = 25_x + 13_y + 3_z ; // 3D point

¿Está hinchado? No , la necesidad está ahí, como lo demuestra la forma en que los complejos C y C ++ necesitan una forma de representar sus valores complejos literales.

¿Está mal diseñado? No , está diseñado como cualquier otra característica de C ++, teniendo en cuenta la extensibilidad.

¿Es solo para fines de notación? No , ya que incluso puede agregar seguridad de tipo a su código.

Por ejemplo, imaginemos un código orientado a CSS:

css::Font::Size p0 = 12_pt ;       // Ok
css::Font::Size p1 = 50_percent ;  // Ok
css::Font::Size p2 = 15_px ;       // Ok
css::Font::Size p3 = 10_em ;       // Ok
css::Font::Size p4 = 15 ;         // ERROR : Won't compile !

Entonces, es muy fácil imponer una tipificación fuerte a la asignación de valores.

¿Es peligroso?

Buena pregunta. ¿Pueden estas funciones tener espacios de nombre? Si es así, entonces Jackpot!

De todos modos, como todo, puedes matarte si una herramienta se usa de forma incorrecta . C es poderoso, y puedes arrancarte la cabeza si haces un mal uso de la pistola C. C ++ tiene la pistola C, pero también el bisturí, el taser y cualquier otra herramienta que encontrará en el kit de herramientas. Puedes usar mal el escalpelo y desangrarte hasta morir. O puedes construir código muy elegante y robusto.

Entonces, como todas las funciones de C ++, ¿realmente lo necesitas? Es la pregunta que debe responder antes de usarla en C ++. Si no lo haces, no te costará nada. Pero si realmente lo necesitas, al menos, el idioma no te fallará.

¿El ejemplo de la fecha?

Su error, me parece, es que están mezclando operadores:

1974/01/06AD
    ^  ^  ^

Esto no se puede evitar porque, al ser un operador, el compilador debe interpretarlo. Y, AFAIK, es algo bueno.

Para encontrar una solución a tu problema, escribiría el literal de alguna otra manera. Por ejemplo:

"1974-01-06"_AD ;   // ISO-like notation
"06/01/1974"_AD ;   // french-date-like notation
"jan 06 1974"_AD ;  // US-date-like notation
19740106_AD ;       // integer-date-like notation

Personalmente, elegiría el entero y las fechas ISO, pero depende de SUS necesidades. Este es el objetivo de dejar que el usuario defina sus propios nombres literales.

Es muy bueno para el código matemático. Fuera de mi mente puedo ver el uso de los siguientes operadores:

grados para grados. Eso hace que la escritura de ángulos absolutos sea mucho más intuitiva.

double operator ""_deg(long double d)
{ 
    // returns radians
    return d*M_PI/180; 
}

También se puede utilizar para varias representaciones de puntos fijos (que todavía se utilizan en el campo de DSP y gráficos).

int operator ""_fix(long double d)
{ 
    // returns d as a 1.15.16 fixed point number
    return (int)(d*65536.0f); 
}

Estos parecen buenos ejemplos de cómo usarlo. Ayudan a hacer más legibles las constantes en el código. También es otra herramienta para hacer que el código sea ilegible, pero ya tenemos tantas herramientas de abuso que una más no duele mucho.

Las UDL tienen espacios de nombre (y se pueden importar mediante declaraciones / directivas, pero no se puede nombrar explícitamente un literal como 3.14std :: i ), lo que significa que (con suerte) no habrá una tonelada de choques.

El hecho de que en realidad puedan estar templados (y constreñidos) significa que puedes hacer cosas bastante poderosas con las UDL. Los autores de Bigint estarán realmente contentos, ya que finalmente pueden tener constantes arbitrariamente grandes, calculadas en tiempo de compilación (a través de constexpr o plantillas).

Me entristece que no veamos un par de literales útiles en el estándar (por su aspecto), como s para std :: string y i para la unidad imaginaria.

La cantidad de tiempo de codificación que se ahorrará con las UDL no es realmente tan alta, pero la facilidad de lectura aumentará enormemente y se pueden pasar más y más cálculos al tiempo de compilación para una ejecución más rápida.

Déjame añadir un poco de contexto. Para nuestro trabajo, los literales definidos por el usuario son muy necesarios. Trabajamos en MDE (Model-Driven Engineering). Queremos definir modelos y metamodelos en C ++. En realidad, implementamos una asignación de Ecore a C ++ ( EMF4CPP ).

El problema surge cuando se pueden definir elementos del modelo como clases en C ++. Estamos adoptando el enfoque de transformar el metamodelo (Ecore) en plantillas con argumentos. Los argumentos de la plantilla son las características estructurales de los tipos y clases. Por ejemplo, una clase con dos atributos int sería algo como:

typedef ::ecore::Class< Attribute<int>, Attribute<int> > MyClass;

Sin embargo, resulta que cada elemento en un modelo o metamodelo, generalmente tiene un nombre. Nos gustaría escribir:

typedef ::ecore::Class< "MyClass", Attribute< "x", int>, Attribute<"y", int> > MyClass;

BUT, C ++, o C ++ 0x no lo permiten, ya que las cadenas están prohibidas como argumentos para las plantillas. Puedes escribir el nombre char por char, pero esto es admirablemente un desastre. Con los literales adecuados definidos por el usuario, podríamos escribir algo similar. Digamos que usamos " _n " para identificar los nombres de los elementos del modelo (no uso la sintaxis exacta, solo para hacer una idea):

typedef ::ecore::Class< MyClass_n, Attribute< x_n, int>, Attribute<y_n, int> > MyClass;

Finalmente, tener esas definiciones como plantillas nos ayuda mucho a diseñar algoritmos para atravesar los elementos del modelo, las transformaciones del modelo, etc. que son realmente eficientes, porque la información de tipo, la identificación, las transformaciones, etc. están determinadas por el compilador en compilación tiempo.

Bjarne Stroustrup habla sobre UDL en este C + +11 talk , en la primera sección sobre interfaces con muchos tipos, alrededor de 20 minutos.

Su argumento básico para las UDL toma la forma de un silogismo:

  1. " Trivial " Los tipos, es decir, los tipos primitivos incorporados, solo pueden detectar errores de tipo trivial. Las interfaces con tipos más ricos permiten que el sistema de tipos detecte más tipos de errores.

  2. Los tipos de errores de tipo que pueden capturar los códigos escritos abundantemente tienen un impacto en el código real. (Da el ejemplo del Mars Climate Orbiter, que infame fracasó debido a un error de dimensiones en una constante importante).

  3. En el código real, las unidades rara vez se utilizan. La gente no los usa, porque incurrir en el tiempo de ejecución o en la sobrecarga de memoria para crear tipos ricos es demasiado costoso, y el uso de un código de unidad con plantilla C ++ preexistente es tan feo que nadie lo usa. (Empíricamente, nadie lo usa, a pesar de que las bibliotecas han existido durante una década).

  4. Por lo tanto, para que los ingenieros utilicen unidades en código real, necesitamos un dispositivo en el que (1) no incurra en gastos generales de ejecución y (2) no sea aceptable a nivel nacional.

La verificación de dimensiones en tiempo de compilación de soporte es la única justificación requerida.

auto force = 2_N; 
auto dx = 2_m; 
auto energy = force * dx; 

assert(energy == 4_J); 

Vea, por ejemplo, PhysUnits-CT-Cpp11 , un pequeño encabezado de C ++ 11, C ++ 14 -Sólo biblioteca para análisis dimensional de compilación y manipulación y conversión de unidades / cantidades. Más simple que Boost.Units , admite unitunité literales como m, g, s, métrica Los prefijos como m, k, M, solo dependen de la biblioteca C ++ estándar, solo SI, potencias integrales de dimensiones.

Hmm ... Aún no he pensado en esta característica. Su muestra fue bien pensada y es ciertamente interesante. C ++ es muy poderoso como lo es ahora, pero desafortunadamente la sintaxis utilizada en los fragmentos de código que lee es a veces demasiado compleja. La legibilidad es, si no todas, al menos mucho. Y tal característica estaría orientada a una mayor legibilidad. Si tomo tu último ejemplo

assert(1_kg == 2.2_lb); // give or take 0.00462262 pounds

... Me pregunto cómo expresarías eso hoy. Tendrías una clase KG y una clase LB y compararías objetos implícitos:

assert(KG(1.0f) == LB(2.2f));

Y eso haría también. Con los tipos que tienen nombres más largos o tipos que no tiene esperanzas de tener un constructor tan agradable para la escritura sin un adaptador, podría ser un buen complemento para la creación e inicialización de objetos implícitos sobre la marcha. Por otro lado, también puede crear e inicializar objetos utilizando métodos.

Pero estoy de acuerdo con Nils en matemáticas. Las funciones de trigonometría de C y C ++, por ejemplo, requieren entrada en radianes. Sin embargo, creo que en grados, una muy buena conversión implícita como Nils publicada es muy buena.

Sin embargo, en última instancia, será azúcar sintáctico, pero tendrá un ligero efecto en la legibilidad. Y probablemente también será más fácil escribir algunas expresiones (el pecado (180.0deg) es más fácil que el pecado (deg (180.0)). Y luego habrá personas que abusan del concepto. Pero luego, las personas que abusan del lenguaje deberían usar lenguajes muy restrictivos en lugar de algo tan expresivo como C ++.

Ah, mi publicación básicamente no dice nada excepto que va a estar bien, el impacto no será demasiado grande. No nos preocupemos. :-)

Nunca he necesitado o deseado esta función (pero esta podría ser el efecto Blub ) . Mi reacción inmediata es que es poco convincente, y es probable que atraiga a las mismas personas que piensan que es genial sobrecargar al operador + para cualquier operación que se pueda interpretar como una adición remota.

C ++ suele ser muy estricto con respecto a la sintaxis utilizada, a menos que el preprocesador no haya mucho que puedas usar para definir una sintaxis / gramática personalizada. P.ej. podemos sobrecargar operatos existentes, pero no podemos definir nuevos - IMO esto está muy en sintonía con el espíritu de C ++.

No me importan algunas formas para un código fuente más personalizado, pero el punto elegido me parece muy aislado, lo que más me confunde.

Incluso el uso previsto puede hacer que sea mucho más difícil leer el código fuente: una sola letra puede tener efectos secundarios de gran alcance que de ninguna manera se pueden identificar en el contexto. Con simetría hacia u, l y f, la mayoría de los desarrolladores elegirán letras individuales.

Esto también puede convertir el alcance en un problema, el uso de letras individuales en el espacio de nombres global probablemente se considerará una mala práctica, y las herramientas que se suponen que mezclan bibliotecas más fácilmente (espacios de nombres e identificadores descriptivos) probablemente anularán su propósito.

Veo algo de mérito en combinación con " auto " ;, también en combinación con una biblioteca de unidades como impulsar unidades , pero no lo suficiente como para merecer esta adición.

Sin embargo, me pregunto qué ideas inteligentes se nos ocurren.

Utilicé literales de usuario para cadenas binarias como esta:

 "asd\0\0\0\1"_b

utilizando el constructor std :: string (str, n) para que \ 0 no corte la cadena a la mitad. (El proyecto hace mucho trabajo con varios formatos de archivo.)

Esto también fue útil cuando dejé std :: string a favor de un contenedor para std :: vector .

El ruido de la línea en esa cosa es enorme. También es horrible leer.

Déjeme saber, ¿razonaron esa nueva adición de sintaxis con algún tipo de ejemplo? Por ejemplo, ¿tienen un par de programas que ya usan C ++ 0x?

Para mí, esta parte:

auto val = 3.14_i

No justifica esta parte:

std::complex<double> operator ""_i(long double d) // cooked form
{ 
    return std::complex(0, d);
}

Ni siquiera si usarías la sintaxis i también en otras 1000 líneas. Si escribes, probablemente escribas 10000 líneas de algo más a lo largo de eso también. Especialmente cuando aún es probable que escriba sobre todo en todas partes esto:

std::complex<double> val = 3.14i
Sin embargo, la palabra clave "auto" puede estar justificada, solo quizás. Pero tomemos solo C ++, porque es mejor que C ++ 0x en este aspecto.

std::complex<double> val = std::complex(0, 3.14);

Es como ... así de simple. Incluso pensé que todos los corchetes estándar y puntiagudos están simplemente flojos si los usa en todas partes. No empiezo a adivinar qué sintaxis hay en C ++ 0x para convertir std :: complex en complex.

complex = std::complex<double>;

Quizás sea algo sencillo, pero no creo que sea tan simple en C ++ 0x.

typedef std::complex<double> complex;

complex val = std::complex(0, 3.14);

¿Quizás? > :)

De todos modos, el punto es: escribir 3.14i en lugar de std :: complex (0, 3.14); no le ahorra mucho tiempo en general, excepto en algunos casos súper especiales.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top