CPPCON2020 Session Review: Back to Basics: The Abstract Machine

If you’ve been flirting with C++, and are past the customary “Hello, world!” program, have been programming for a few months, but want to commit to a serious relationship, then this talk is for you.

Bob Steagall masterfully walks you through the C++ abstract machine, and take you to the dance between the programmer, the compiler, the abstract machine, the machine code and the physical (or in today’s world, virtual) machine. You can also check Bob’s Blog.

This post from last year caught my eye:
Programming language popularity: C++ bounces back at Python’s expense

Now, C++ and Python are different tools for different problems, and I see them as complementary–just check TensorFlow, which harmonizes both languages beautifully. The key point is that C++ usage is growing. If you want to be part of this growth, check this talk.

Bob starts with the language goals, and focuses on performance-critical software, and no room for a lower level language, and no, this is not a conspiracy to “dislodge” C. How does C++ meet such goals? Well, dig in and learn about the abstract machine

You will learn about the differences between programmer’s concerns vs. the compiler’s concerns, expressions, definitions of what an abstract machine for C++ is, its parametrization, what a well-formed program is, and so on. Bob covers nondeterminism, implementation-defined behavior, unspecified behavior, undefined behavior (the dreaded UB, and nope, it’s not Ultra Biolet, where Biolet is a not a biological product, and…but I digress).

So, if you’re serious about your relationship with C++, or simply want to review the basics, I invite you to check this talk. Since none of us like reading standardese (well, some do), you can find additional documentation about the C++ language in bite-sized chunks of text with examples on cppreference, or in Spanish on es.cppreference. A parting quote from Bob’s talk:

When we write C++ code, we are writing to the C++ abstract machine

Thus Spoke Bob Steagall.

Posted in C++, C++17, C++20, programming languages, software | Tagged , , , , | Leave a comment

CPPCON2020 Session Review: Template Metaprogramming Type Traits (Part 1 and Part 2)

I attended this pair of sessions by Jody Hagins, and then went back and watched them again at unholy hours in the morning.

Note: As an attendee, I can munch on the sessions before they’re officially posted on the YouTube CPPCON Channel, in case you’re wondering why you can’t access them Once they’re availlable, munch away!.

These sessions are easy going, with a ping back to Walter Brown’s “seminal” pair of corresponding talks, Modern Template Metaprogramming: A Compendium: Part I and Modern Template Metaprogramming: A Compendium: Part II, both given during CPPCON2014 and at the end of this post for your enjoyment. If you have half a day to spend munching on metaprogramming, this set of four talks should get you going on the road to master template metaprogramming, grasshopper.

Before you start munching, you can find the Type Traits referred in these talks in cppreference, or, if you prefer Spanish, you can find them in es.cppreference,

Holy Cow, the Book of Coplien!

Couldn’t help but smile when Jody pulled out a copy of the “Purple Book” of Advanced C++. That’s where I learned the word “functor”. Used to keep a copy–along with a copy of the C++ ARM (when ARM was an acronym for Annotated Reference Manual) but alas, both have been lost in time.

With the homage to Coplien and Boost Type Traits, pioneers and base for what we now see in C++ type traits, correspondingly, Jody digs in.

Part 1

In short, the first session , convention for type traits that yield a ::value, that yield a ::type, value metafunctions (those using a _v suffix), type metafunctions (those using a _t suffix; Jody peels away the mystery of std::integral_constant and ventures into the is_void as an example on how to remove cv-qualifiers.

Part 2

Part 2 picks up with a review of the different categories of type traits (primary, composite type, type properties, etc.). Template specialization will be used abundantly, so be ready. A cursory review will help.

Now, in case you wondered, SFINAE is not a cursing word in Spanish–or any other language for that matter, but, along with CRTP, and RAII, it’s one of those acronyms that once you’ve spent time in C++, you’ll learn by heart.

How About those Other Talks?

Walter Brown’s Talks can be found here:

Posted in software | Tagged , , , , , , | Leave a comment

Boutique C++

This year I’ve enjoyed two excellent C++ books: C++ Move Semantics – The Complete Guide, by Nico Josuttis, and C++ Lambda Story, by Bartłomiej Filipek, both members of the C++ Standards Committee.

Updated on July 19, 2021
For a printed copy of C++ Lambda Story on Amazon, USA, click one of the following links:
C++ Lambda Story (in black and white)
C++ Lambda Story (in full color)

Updated on September 9, 2020
For a printed copy of C++ Move Semantics – The Complete Guide on Amazon, USA, click the following link: C++ Move Semantics – The Complete Guide

After reading each of the books–and like pulp fiction novels, were written in installments–I had this feeling of having enjoyed the books very much, and learned so much in the process, and kept wondering why that was.

The reason, as I describe it, is a boutique approach to writing. That is, focus on one topic, and exhaust the topic to the end. I live in an area in Southern California rich in vineyards, and it is common to find boutique wineries that specialize in one specific kind of grape or wine, and couldn’t help but compare the approach that both Nico and Bartek have taken.

Sure, who hasn’t heard or learned about move semantics, with the original proposal here–and initial attempts with Andrei Alexandrescu’s Mojo library way back when. I go back to a white paper/blog post by Stephan T. Lavavej many, many moons ago, or the sessions about lambdas by Herb Sutter in the Microsoft Build conferences, and of course, paying homage to Scott Meyers Effective Modern C++ and universal references.

One problem, as I see it, is that often times you don’t have time to keep up with the new and evolving language features. Living in a polyglot world, you have to pick up different tools for the job, from C++ to Python to Java to Go to TypeScript to…you get the idea. Furthermore, although new features are great, by the time you get to them, well, a new Standard is out. I bet that the next CPPCON will be talking about C++23. Just get that feeling.

If you’re like me, you read maybe a couple of books during a given year–and I mean read, as in end to end. The challenge that I have is always the time available for such activity. Alas, there’s a family to attend to, and weekends I own the kitchen. So, what do you pick? I like the King Kong size references that I trust and I can always go back to, munching on them a little at a time, but I found myself going back to these books for more. The reason? They’re filled with details, examples that you can whip up in a few seconds or in a few minutes, and quality material. It is easier on the author, too, because she/he can concentrate on a specific topic.

So saying that, and hoping to encourage more authors, if I had my druthers, I’d like to see a boutique book on:

  • Concepts (and type traits in the process)
  • Coroutines

Any takers?

And yes, se habla C++.

Posted in software | Tagged , , , , | 4 Comments

HOME: Habitations and Observations for Moon Exploration

How would you go about developing a habitat for five people to survive six months on the Moon?

You may be familiar with Artemis, whose goal is to “land the first woman and the next man on the Moon by 2024, using innovative technologies to explore more of the lunar surface than ever before”.

These past weeks I’ve been following the progress of a team of summer interns for NASA, our daughter being one of the members, as they designed such a system. Alas, it will come to an end after they present their design in a couple of days

NASA provided two outstanding mentors, as well as resources, interviews, and challenged the interns to come up with fresh ideas. One of the presenters was Jerry Woodfill, who has been working for NASA for over 50 years and had an entertaining and eye opening session explaining all the things that went wrong and right with the Apollo 13 mission.

It has been such a joy to see how the team coalesced and converged on a design after many weeks of work, and it gives you an idea of what a multi-discipline team can do.

So, if you enjoy a focused presentation and pushing the next generation of engineers, go ahead and check their presentation on YouTube.

Luna, luna,
dame una tuna,
la que me diste
cayó en la laguna.

–Rima popular

Posted in software | Tagged , , , | Leave a comment

The Whole Enchilada: A C++ Template Recipe

Tortilla press / Prensa para tortillas

Most likely you’ve had enchiladas, but have you ever heard of entomatadas? They’re a great alternative for those that don’t like “fire in the belly” during/post eating enchiladas and they follow the same process, except that instead of chili you use fresh tomatoes.

The secret to a good plate of enchiladas/entomatadas is in the sauce. While at home we use home-made corn tortillas, you’ll be just fine with regular corn tortillas. If you leave nearby a Mexican food market (such as Cardenas in Southern California), or a corn-tortilla factory, the tortillas will be of better quality. Just saying.

The following are two recipes that you can enjoy at home without spending too much or going out. Accompany with Mexican rice and refried beans (recipes not included).

And of course, there’s some C++ involved.

Enchiladas

Ingredients

  1. Two bags of dry Anaheim or Pasilla red-chili peppers with approx. 10 chili pods will give you a good 10 enchiladas
    Make sure they’re not the skinny chilis (chile del árbol), those are very, very hot. You’ll thank me.
  2. Cheese to you heart’s content.
    Monterey, cheddar/monterey mixed, queso fresco, grated cheese
  3. Olive oil or any oil of your preference. As you may have heard in a movie, “olive oil caresses your insides, leaving nothing but its essence.”
  4. A teaspoon of flour
    The flour will be used to cook the chili sauce and help it thicken. Don’t use corn starch.

Procedure

  1. Grate the cheese, set it aside.
  2. Trim the stem of the chili pods, take out the seeds by simply shaking the chilis and wash them.
    Sometimes chilis have some dust on them.
  3. Soak the chilis in warm water for approximately 1 hour
    This softens the inside of the chili and it let’s out its “meat”.
  4. Blend the chili in a blender with a little bit of the water used to soak the chilis.
    Make sure you don’t use too much water in the blender, otherwise you will end up with watery sauce. You’ll have to guess how much water to use, but it’s best to start with a little bit.
  5. Strain the sauce in the blender with a fine strainer. Place the strained sauce in a container.
    If you don’t use a fine strainer, you may end up with chili husk in the sauce.
    Press the chili in the strainer with a spoon to get as much sauce as possible.
    If there is still sauce that can be extracted, return the chili husks to the blender, add a bit of water to the blender, and repeat the previous step, then strain again.
  6. Heat a teaspoon of oil in a sauce pan to medium heat. Then add a teaspoon of flour and cook it until it’s light brown. Add the enchilada sauce and cook in low heat until it comes to a boil, then set aside.
    The sauce pan has to be large enough where for you to soak a corn tortilla through the pan.
  7. Add oil to a skillet or pan where you can fry the corn tortillas.
    You need to have enough oil to deep-fry a few corn tortillas.
  8. Fry the tortilla (15-20 seconds per side)
    Make sure it’s neither too soft nor too hard. If it’s too soft, it’ll come apart in the sauce pan. If it’s too hard, you can “soften” it by leaving it some extra time in the sauce pan or reheating in the microwave once it goes through the sauce.
  9. Extract the tortilla and soak it in the sauce.
  10. Place in a large plate and add cheese on top.
  11. Repeat from step 8.

Enchiladas Montadas

You may have heard of enchiladas montadas. It’s basically a topping of your choice. Typical choices are an over-easy egg (huevo estrellado) or tuna with lemon/lime juice and tiny bit of salt.

Simply top your stack of enchiladas with one of those toppings.

Entomatadas

Ingredients

  1. 4-6 fresh, medium steak tomatoes will give you a good 10-12 entomatadas.
  2. A small can (1 cup) of tomato sauce for color and thickness.
    Make sure it’s not tomato paste.
  3. Cheese to you heart’s content.
  4. Olive oil or any oil of your preference.
  5. A teaspoon of flour
    The flour will be used to cook the chili sauce and help it thicken. Don’t use corn starch.

Procedure

  1. Grate the cheese, set it aside.
  2. Boil the tomatoes until they’re soft. Usually a few minutes after they come to a boil.
    Don’t wait until they come apart from over-boiling.
  3. Peel the tomatoes and blend them with a little bit of salt.
    Unlike the enchiladas recipe, you don’t need to add water. Tomatoes are already watery.
  4. Heat a teaspoon of oil in a sauce pan to medium heat. Then add a teaspoon of flour and cook it until it’s light brown. Add the sauce in the blender and the tomato sauce and cook in low heat until it comes to a boil, then set aside.
    The sauce pan has to be large enough where for you to soak a corn tortilla through the pan.
  5. Add oil to a skillet or pan where you can fry the corn tortillas.
    You need to have enough oil to deep-fry a few corn tortillas.
  6. Fry the tortilla (15-20 seconds per side)
    Make sure it’s neither too soft nor too hard. If it’s too soft, it’ll come apart in the sauce pan. If it’s too hard, you can “soften” it by leaving it some extra time in the sauce pan or reheating in the microwave once it goes through the sauce.
  7. Extract the tortilla and soak it in the sauce.
  8. Place in a large plate and add cheese on top.
  9. Repeat from step 6.

Optional

If you like your entomatadas spicy, just boil one or two jalapeño chilis along with the tomatoes and blend them together. Furthermore, you can also have entomatadas montadas by adding the toppings mentioned above.

Given the ingredients and procedure, it is evident that once you’re done with the sauce, the procedure is the same. Let’s add a function template for the algorithm to cook the enchiladas/entomatadas:

#include <chrono>
#include <stack>

// T is either chili or tomatoe. No time to use enable_if
template<typename T, typename Oil, typename Cheese>
std::stack<T> make_it(std::stack<corn_tortilla> tortillas, Sauce<T> sauce, Oil oil, Cheese ch) {
  static_assert(cheese.is_grated(), "Grate the cheese, por favor!");
  
  std::stack<corn_tortilla> plate;

  stove s{}; // generic stove

  // Sauce and oil act on objects imbued (stove, pan),
  // that way we can refer to "heat the oil"
  sauce.imbue(s);
  sauce.imbue(pan{});
  sauce.heat(heat_level::medium);
  sauce.heat(heat_level::none); // turn off the heat

  oil.imbue(s);
  oil.imbue(pan{}); 
  oil.heat(heat_level::medium);

  while (!tortillas.empty()) {
    tortilla = tortillas.top();
    tortillas.pop();

    using namespace std::chrono_literals;
    tortilla.fry(oil, 15s);
    tortilla.flip();
    tortilla.fry(oil, 15s);
    sauce.soak(tortilla, 10s);
    
    plate.push(tortilla);     // move the enchilada/entomatada to the stack
    add_cheese(tortilla, ch); // implementation-defined
    
  }
} // Oil object will call heat(heat_level::none) in the dtor.


Enjoy. Stay safe. Stay healthy.

Posted in software | Tagged , , | Leave a comment

Una historia de conversión: mejorando from_chars y to_chars en C++17

El Domo del Panteón, Roma

NOTA: Puedes obtener el código fuente y ejemplos en Github en https://github.com/ljestrada/charconvutils y ver mi charla CPPCON2019 Lightning Challenge en YouTube aquí, y la versión en inglés de esta publicación aquí.

No, no se trata de una experiencia religiosa, epifanía, cambio de fe o de un encuentro con mi creador. Más bien, es sobre C++.

C++17 proporciona dos funciones de conversión de bajo nivel,  std::from_chars y std::to_chars, en el archivo de encabezado <charconv>, pero tienen un modelo de uso que puede mejorarse fácilmente usando el poder de las plantillas.

from_chars y to_chars son atractivas porque tienen un número de garantías agradables: independientes de la configuración regional (locale), no asignan memoria y no lanzan excepciones. Compara std::to_chars con std::to_string, que es dependiente de la configuración regional y puede lanzar bad_alloc. Dicho esto, las funciones son en realidad un conjunto de funciones sobrecargadas para tipos enteros, char, y tipos de punto flotante (float, double y long double). Observa que bool no se soporta, ni tampoco los otros tipos de caracteres.

Nicolai Josuttis tiene una cobertura excelente y dedica un capítulo completo a estas funciones en su libro C++17 – La guía completa. Ahí podrás encontrar ejemplos de from_chars y to_chars usando vínculos estructurados e if con un inicializador, otras dos nuevas adiciones a C++17. Además, puedes referirte a la documentación en es.cppreference.com para más ejemplos.

Las signaturas de las funciones para conversión de caracteres from_chars son las siguientes:

std::from_chars_result from_chars(
  const char* first, const char* last, /*véase descripción*/& value,
  int base = 10);

std::from_chars_result from_chars(
  const char* first, const char* last, float& value, 
  std::chars_format fmt = std::chars_format::general);

std::from_chars_result from_chars(
  const char* first, const char* last, double& value,
  std::chars_format fmt = std::chars_format::general);

std::from_chars_result from_chars(
  const char* first, const char* last, long double& value,
  std::chars_format fmt = std::chars_format::general);

struct from_chars_result {
    const char* ptr;
    std::errc ec;
};  

La primer signatura es para tipos enteros. El comentario /*véase descripción*/ se refiere a los tipos enteros que se soportan, así como a char (nuevamente, pero no a bool o a cualquier otro de los tipos de caracteres). El resto de las signaturas son para los tipos de punto flotante. La estructura from_chars_result se utiliza para almacenar el resultado. Aunque las signaturas de las funciones no están marcadas con el especificador noexcept, la documentación declara que no lanzan excepciones–los errores se comunican mediante el miembro ec en la estructura from_chars_result. Como una anécdota, la implementación de Microsoft “refuerza” las funciones marcándolas con el especificador noexcept.

De manera similar, las signaturas de las funciones para las funciones de conversión de caracteres to_chars son las siguientes:

std::to_chars_result to_chars(
  char* first, char* last, /*véase descripción*/ value, int base = 10);

std::to_chars_result to_chars(char* first, char* last,
  float value);

std::to_chars_result to_chars(char* first, char* last,
  double value);

std::to_chars_result to_chars(char* first, char* last,
  long double value);

std::to_chars_result to_chars(char* first, char* last,
  float value, std::chars_format fmt);

std::to_chars_result to_chars(char* first, char* last,
  double value, std::chars_format fmt);

std::to_chars_result to_chars(char* first, char* last,
  long double value, std::chars_format fmt);

std::to_chars_result to_chars(char* first, char* last,
float value, std::chars_format fmt, int precision);

std::to_chars_result to_chars(char* first, char* last,
  double value, std::chars_format fmt, int precision);

std::to_chars_result to_chars(char* first, char* last,
long double value, std::chars_format fmt, int precision);

struct to_chars_result {
    char* ptr;
    std::errc ec;
}; 

El modelo de uso requiere que siempre proporciones dos punteros a char. Considera la conversión de una secuencia de caracteres a un tipo de punto flotante usando from_chars:

#include <charconv>

int main() {
  char a[] = "3.141592";
  float pi;
  std::from_chars_result res = std::from_chars(a, a+9, pi);
  if (res.ec != std::errc{} ) {
    // CONVERSIÓN FALLÓ
  }
}

La función sobrecargada que toma un float se seleccionará y el resultado se colocará en la variable pi.

De manera similar para convertir una variable de punto flotante en una secuencia de caracteres usando to_chars:

#include <charconv>

int main() {
  char a[10];
  float pi = 3.141592f;

  std::to_chars_result res = std::to_chars(a, a+10, pi);
  if (res.ec != std::errc{} ) {
    // CONVERSIÓN FALLÓ
  }
}

Véase la documentación para el valor del miembro ptr cuando se tiene éxito o existe un error.

Aun cuando el modelo de uso es consistente, uno no puede evitar notar que para los casos en los que se conoce el tamaño del array, uno tiene que pasar un puntero de más. ¿Qué tal si en su lugar pudiéramos usar?:

#include "charconvutils.hpp" // plantillas y polvo de hadas

using charconvutils::from_chars, charconvutils::to_chars;

int main() {
  char in[] = "3.141592";
  char out[50]; 
  float pi;

  // Mira, mami, no rastreo el tamaño del array
  std::from_chars_result res1 = from_chars(in, value);
  std::to_chars_result   res2 = to_chars(out, pi);
}

El modelo de uso se simplifica: no tienes que rastrear el tamaño del array. Además, puedes usar otras clases tales como std::array, std::string, std::string_view o std::vector. ¿Interesado? Sigue leyendo.

Plantillas de función al rescate

Examinando cuidadosamente las signaturas de las funciones, hacemos las siguientes observaciones:

  • Todas la funciones toman un par de [const] char* que constituyen un rango válido. En el caso de from_chars, el rango es de sólo lectura, en el caso de to_chars, es donde se ubicará la salida.
  • Las funciones están sobrecargadas para tipos enteros, char y tipos de punto flotante.

Una primera pasada a una plantilla de función para from_chars podría verse así (??? es información que se suministrará posteriormente):

template<std::size_t N, typename T>
std::from_chars result
from_chars(const char(&a)[N], T& value, ???)
{
  return std::from_chars(a, a+N, value, ???);
}

Este cascarón inicial toma ventaja de la deducción de argumentos de plantilla para determinar el tamaño del array. Como el array se pasa por referencia (eso es lo que hace la sintaxis const char(&a)[N), no decae a un puntero, y es lo que queremos.

De manera similar, una primera pasada a una plantilla de función para to_chars podría verse así:

template<std::size_t N, typename T>
std::to_chars result
to_chars(const char(&a)[N], T& value, ???)
{
  return std::to_chars(a, a+N), value, ???);
}

Aquí tienes una versión que no funciona de las plantillas de función usando este enfoque, limitada a from_chars por simplicidad:

// Para tipos enteros
template<std::size_t N, typename T>
std::from_chars_result
from_chars(const char(&a)[N], T& value, int base = 10)
{
  return std::from_chars(a, a + N, value, base);
}

// Para tipos de punto flotante
template<std::size_t N, typename T> 
std::from_chars_result
from_chars(const char(&a)[N], T& value,
           std::chars_format fmt = std::chars_format::general)
{
  return std::from_chars(a, a + N, value, fmt);
}

Ejecutar esta versión de las plantillas de función de cierto modo funciona, pero se rompe cuando utilizas un tipo de punto flotante que no suministra un argumento base, que se initialize por defecto a 10, y por pensar que se instanciará la segunda plantilla de función, pero este no es el caso. Cuando se llaman con el mismo número de argumentos, la primer plantilla de función será instanciada confloat/double/long double, y la llamada a std::from_chars no corresponde.

Invertir las definiciones de las plantillas de función–primero la función para tipos de punto flotante, luego la función para tipos enteros–tampoco funciona.

Lo que queremos es una manera de instanciar la primera plantilla de función cuando pasamos un tipo entero, y la segunda cuando pasamos un tipo de punto flotante. Lo que necesitamos son plantillas y un poquito de polvo de hadas:

#include "charconvutils.hpp" // plantillas y polvo de hadas

using charconvutils::from_chars, charconvutils::to_chars; 

int main() {
  char a[] = "365";
  int daysPerYear;

  // Crea una copia de una plantilla de función para tipos enteros
  std::from_chars_result res1 = from_chars(a, daysPerYear);

  char b[] = "3.141592";
  float pi;

  // Crea una copia de una plantilla de función para tipos
  // de punto flotante
  std::from_chars_result res2 = from_chars(b, pi);
}

Por supuesto, usar la función con parámetros adicionales también permitiría al compilador escoger la función correcta, tal como proporcionar una base para los tipos enteros o fmt para los tipos de punto flotante.

#include "charconvutils.hpp" // plantillas y polvo de hadas

using charconvutils::from_chars, charconvutils::to_chars; 

int main() {
  char a[] = "365";
  int daysPerYear;

  // Crea una copia de una plantilla de función para tipos enteros
  std::from_chars_result res1 = from_chars(a, daysPerYear, 10);

  char b[] = "3.141592";
  float pi;

  // Crea una copia de una plantilla de función para tipos
  // de punto flotante
  std::from_chars_result res2 = from_chars(b, pi);
}

Presentando a enable_if

La solución es usar el rasgo de tipo enable_if, disponible en el archivo de encabezado <type_traits>, o más bien su alias de plantilla enable_if_t. Este rasgo de tipo produce void cuando se le pasa un argumento, o un tipo dado cuando se le pasan dos argumentos y el primero produce true.

El polvo de hadas detrás de enable_if es que si la generación de la copia falla, la plantilla en cuestión no será creada. Estupendo. Al usar otros dos rasgos de tipo, is_integral e is_floating_point, definimos dos alias de plantilla auxiliares que nos permitirán discriminar entre tipos enteros y tipos de punto flotante y seleccionar la plantilla de función apropiada. Un pequeño detalle es que is_integral incluye bool y tipos de carácter además de char, pero para nuestros propósitos, será suficiente–podemos dejar al compilador para que rechace todas las otras copias. Aún más, usaremos los alias de plantilla is_integral_v e is_floating_point_v, disponibles desde C++17, para simplificar.

// Alias de plantilla para tipos enteros
template<typename T>
using EnableIfIntegral = 
  std::enable_if_t<std::is_integral_v<T>>;

// Alias de plantilla para tipos de punto flotante
template<typename T>
using EnableIfFloating =
  std::enable_if_t<std::is_floating_point_v<T>>;


NOTA: El rasgo de tipo std::is_integral incluye bool, tipos de carácter y tipos enteros. Las funciones de conversión std::from_chars y std::to_chars solamente consideran tipos enteros, char y tipos de punto flotante. Ten en mente que el alias de plantilla EnableIfIntegral filtrará cualquier cosa permitida por la plantilla std::is_integral.

Con los alias de plantilla a la mano, definimos dos plantillas de función para la función de conversión de caracteres from_chars (la extenderemos a otros tipos además de arrays de caracteres posteriormente):

template<std::size_t N, typename T, typename=EnableIfIntegral<T>>
std::from_chars_result
from_chars(const char(&a)[N], T& value, int base = 10)
{
  return std::from_chars(a, a + N, value, base);
}

template<std::size_t N, typename T  typename=EnableIfFloating<T>>
std::from_chars_result
from_chars(const char(&a)[N], T& value,
   std::chars_format fmt = std::chars_format::general)
{
  return std::from_chars(a, a + N, value, fmt);
}

De manera similar, definimos tres funciones de plantilla para la función de conversión de caracteres to_chars. Observa que las plantillas de función no tienen un formato o precisión por defecto:

template<std::size_t N, typename T, typename=EnableIfIntegral<T>>
std::to_chars_result
to_chars(char(&a)[N], T value, int base = 10)
{
  return std::to_chars(a, a + N), value, base);
}

template<std::size_t N, typename T, typename=EnableIfFloating<T>> 
std::to_chars_result
to_chars(char(&a)[N], T value, std::chars_format fmt)
{
  return std::to_chars(a, a + N), value, fmt);
}

template<std::size_t N, typename T, typename=EnableIfFloating<T>>  
std::to_chars_result
to_chars(char(&a)[N], T value, std::chars_format fmt,
         int precision)
{
    return std::to_chars(a, a + N), value, fmt, precision);
}

Extender a std::array

Es fácil extender la funcionalidad a std::array. Las plantillas de función from_chars y to_chars correspondientes para std::array son directas. Aquí tienes un par de ejemplos para tipos enteros:

template<std::size_t N, typename T, typename=EnableIfIntegral<T>>
std::from_chars_result
from_chars(const std::array<char, N>& a, T& value, int base = 10)
{
  return std::from_chars(a.data(), a.data() + N, value, base);
}

template<std::size_t N, typename T, typename=EnableIfIntegral<T>> 
std::to_chars_result
to_chars(std::array<char, N>& a, T value, int base = 10)
{
  return std::to_chars( a.data(), a.data() + N, value, base);
}

Extender a contenedores de acceso aleatorio

En Programación Genérica y la STL, Matt Austern describe los contenedores de acceso aleatorio. El libro se basa en la documentación de SGI STL (o viceversa) y Martin Broadhurst amablemente la conservó aquí. Puedes encontrar la descripción de contenedores de acceso aleatorio en inglés aquí. Diciendo esto, std::string, std::vector, std::deque y std::array son contenedores de acceso aleatorio que podrían usarse como la fuente o el destino de from_chars and to_chars. Simplemente asigna el almacenamiento necesario para la conversión por adelantado. std::string_view, aunque estrictamente hablando no es un contenedor de acceso aleatorio, califica porque proporciona la misma interfaz, accediendo a su std::string subyacente. Por supuesto, uno deberá garantizar que los contenedores no cambiarán mientras la conversión toma lugar.

Con las definiciones que siguen, las plantillas de función para std::array definidas anteriormente pueden eliminarse y reemplazarse.

La plantilla de función calcula el rango en tiempo de ejecución utilizando data() y size(). Además, hay una comprobación para evitar pasar contenedores cuyo tipo de valor no es char. Aquí tienes un par de ejemplos para tipos enteros:

template<typename Cont, typename T, typename=EnableIfIntegral<T>>
std::from_chars_result
from_chars(const Cont& c, T& value, int base = 10)
{
  static_assert(std::is_same_v<char, typename Cont::value_type>,
                "Container value type must be char.");
  return std::from_chars(c.data(), c.data() + c.size(), 
                         value, base);
}

template<typename Cont, typename T, typename=EnableIfIntegral<T>> 
std::to_chars_result
to_chars(Cont& c, T value, int base = 10)
{
  static_assert(std::is_same_v<char, typename Cont::value_type>,
                "Container value type must be char.");
  return std::to_chars(c.data(), c.data() + c.size(),
                       value, base);
}

En resumen

Aquí tienes el archivo "charconvutils.hpp" completo:

#ifndef CHARCONVUTILS_HPP
#define CHARCONVUTILS_HPP

#include <charconv>
#include <type_traits>

namespace charconvutils {

// -----
// Alias de plantilla asistentes

template<typename T>
using EnableIfIntegral = 
  std::enable_if_t<std::is_integral_v<T>>;

template<typename T>
using EnableIfFloating = 
std::enable_if_t<std::is_floating_point_v<T>>;

// -----
// plantillas de función de from_chars para arrays estilo C

template<std::size_t N, typename T,
         typename =  EnableIfIntegral<T>>
std::from_chars_result
from_chars(const char(&a)[N], T& value, int base = 10) {
  return std::from_chars(a, a + N, value, base);
}

template<std::size_t N, typename T, 
         typename =  EnableIfFloating<T>>
std::from_chars_result
from_chars(const char(&a)[N], T& value,
           std::chars_format fmt = std::chars_format::general) {
  return std::from_chars(a, a + N, value, fmt);
}

// -----
// plantillas de función de from_chars para contenedores de
// acceso aleatorio

template<typename Cont, typename T,
         typename = EnableIfIntegral<T>>
std::from_chars_result
from_chars(const Cont& c, T& value, int base = 10) {
  static_assert(std::is_same_v<char, typename Cont::value_type>, 
                "Tipo de valor del contenedor debe ser char.");
  return std::from_chars(c.data(), c.data() + c.size(),
                         value, base);
}

template<typename Cont, typename T,
         typename = EnableIfFloating<T>>
std::from_chars_result
from_chars(const Cont& c, T& value,
           std::chars_format fmt = std::chars_format::general) {
  static_assert(std::is_same_v<char, typename Cont::value_type>, 
                "Tipo de valor del contenedor debe ser char.");
  return std::from_chars(c.data(), c.data() + c.size(),
                         value, fmt);
}

// -----
// plantillas de función de to_chars para arrays estilo C

template<std::size_t N, typename T,
         typename = EnableIfIntegral<T>>
std::to_chars_result
to_chars(char(&a)[N], T value, int base = 10) {
  return std::to_chars(a, a + N, value, base);
}

template<std::size_t N, typename T,
         typename = EnableIfFloating<T>>
std::to_chars_result
to_chars(char(&a)[N], T value, std::chars_format fmt) {
  return std::to_chars(a, a + N, value, fmt);
}

template<std::size_t N, typename T,
         typename = EnableIfFloating<T>>
std::to_chars_result
to_chars(char(&a)[N], T value, std::chars_format fmt,
         int precision) {
  return std::to_chars(a, a + N, value, fmt, precision);
}

// -----
// plantillas de función de to_chars para contenedores
// de acceso aleatorio

template<typename Cont, typename T,
         typename = EnableIfIntegral<T>>
std::to_chars_result
to_chars(Cont& c, T value, int base = 10) {
  static_assert(std::is_same_v<char, typename Cont::value_type>, 
                "Tipo de valor del contenedor debe ser char.");
  return std::to_chars(c.data(), c.data() + c.size(),
                       value, base);
}

template<typename Cont, typename T,
         typename = EnableIfFloating<T>>
std::to_chars_result
to_chars(Cont& c, T value, std::chars_format fmt) {
  static_assert(std::is_same_v<char, typename Cont::value_type>, 
                "Tipo de valor del contenedor debe ser char.");
  return std::to_chars(c.data(), c.data() + c.size(),
                       value, fmt);
}

template<typename Cont, typename T,
         typename = EnableIfFloating<T>> 
std::to_chars_result
to_chars(Cont& c, T value, std::chars_format fmt, int precision) {
  static_assert(std::is_same_v<char, typename Cont::value_type>,
                "Tipo de valor del contenedor debe ser char.");
  return std::to_chars(c.data(), c.data() + c.size(),
                       value, fmt, precision);
}

} // namespace charconvutils

#endif // CHARCONVUTILS_HPP

Puedes descargar este archivo (con comentarios en inglés) en GitHub.

Notas posteriores

Jens Maurer propuso las funciones de conversión de caracteres en esta ponencia. Es interesante ver las interfaces propuestas, y no pude encontrar por qué no se consideraron plantillas (aunque se consideró std::string_view). Quizás en una iteración distinta de la ponencia la idea se rechazó y pasé algo por alto (p. ej., consideraciones para que los contenedores no sean accedidos por múltiples hilos mientras la conversión toma lugar).

¿Qué tal vínculos estructurados? Seguro, puedes usar vínculos estructurados para capturar la estructura resultante–como se mencionó previamente, Nicolai Josuttis tiene varios ejemplos.

Otro aspecto que parece faltar es, ¿si se declara que las funciones no lanzan excepciones, por qué no marcarlas con noexcept? Eso es lo que Microsoft hizo, y parece lógico. ¿Quizás una consideración es que el rango pueda volverse inválido mientras se realiza la conversión?

En resumen, este artículo exploró cómo se puede mejorar el modelo de uso para las funciones de conversión de caracteres invirtiendo en plantillas de función.

Si quieres aprender más sobre las nuevas características de C++17, adquiere el libro electrónico o en Amazon. Los enlaces los puedes encontrar aquí.

Posted in C++, C++17, software | Tagged , , , , | 1 Comment

Habemus C++20!

The first blog posts have started to appear and Herb Sutter already posted a trip report.

C++20 brings about a King-Kong-number of changes, and of course, the Big Kahuna:

  • Concepts
  • Ranges
  • Coroutines
  • Modules

You can find an (unofficial) summary of the features for C++20 on cppreference.com or your preferred website for C++ documentation.

En español

Los primeros blogs ya empezaron a aparecer y Herb Sutter ya postuló su informe de viaje.

C++20 trae consigo un número de cambios tamaño King Kong y, por supuesto, la Gran Kahuna:

  • Conceptos
  • Rangos
  • Corutinas
  • Módulos

Puedes encontrar un resumen (extraoficial) de las características de C++20 en es.cppreference.com (en español) o tu sitio web favorito para documentación de C++.

Posted in C++, C++20, software | Tagged , | Leave a comment

C++17 – La guía completa

Actualizado el 10 de febrero de 2020
Los siguientes son los enlaces en Amazon:
Amazon España
Amazon México
Amazon Estados Unidos
Amazon Canadá

Como un estudiante de ingeniería en mi amada alma mater, el Instituto Tecnológico de Chihuahua, recuerdo claramente durante una de nuestras clases de electrónica al Profesor Nevárez dándonos el material, disponible en la biblioteca de la escuela…en inglés. También recuerdo claramente esos libros amarillentos y gruesos, con un montón de dibujos de circuitos escritos en un lenguaje que, aunque podía leerlo, era difícil captar todo el contenido.

Caray, en ese tiempo hablaba poco inglés, habiéndolo aprendido a través de la secundaria, la preparatoria, mis viajes con mi abuela y mis tías durante el verano, traduciendo canciones, y cualquier otra manera que tenía a mi alcance, pero nunca intenté leer un libro técnico.

Sin embargo, tenía una ventaja en el sentido que el esfuerzo para mí de entender el material sería menor. Pero no tenía que haber sido de esa manera si hubiera habido material fácilmente disponible en español, pero no lo había–al menos no para esa clase en particular.

Sacando fruto de esa experiencia, me puse en contacto con Nico. Le había estado suministrando retroalimentación de su libro, C++17 – The Complete Guide, y le propuse la idea de traducirlo al español. Como le expliqué a Nico ese día, mi propósito principal era y todavía es traer un libro de C++ de alta calidad para estudiantes y profesionistas hispanohablantes.

Puedes ver (o mejor dicho, leer) el resultado de esa colaboración.

Trabajar con Nico has sido una experiencia instructiva. Es un profesional en toda la extensión de la palabra y siempre se conduce de una manera profesional. Además, entender la cantidad de trabajo que conlleva crear un libro, inclusive después que ha sido traducido, a un producto final, realmente me abrió los ojos. En una conversación casual que tuve con Rainer Grimm en CPPCON 2018, mencionó que después que Nico publicaba un libro, había poco material que no se cubría, o huecos que no se llenaran–o palabras similares. Viniendo de Rainer, un autor renombrado, es mucho que decir acerca de un colega. Habiendo visto el trabajo de Nico de cerca, sólo puedo agregarme a ese comentario.

Traducir un libro es una tarea desafiante. No sólo tienes que traducir las palabras, pero tienes que hacerlo tomando en cuenta el estilo y los términos técnicos, y con las diferentes variantes del español, asegurarte que no utilices términos que son específicos para un país–p. ej., México. Es por eso que tengo que dar gracias a José Daniel García. José Daniel también es un miembro del Comité de Normalización de C++ en España, y respondió pacientemente a mis preguntas en cuanto a uso de términos técnicos, sugerencias y un mejor flujo del material.

Creo firmemente que este libro llena un vacío en el material fácilmente disponible para C++ en español, y es mi esperanza que asista a otros al aprender este hermoso lenguaje de programación que he usado desde 1996 y durante años para programar sistemas para el mercado de seguridad pública, 9-1-1, para salvar vidas diariamente.

Si te interesa comprarlo o recomendarlo a tus amigos hispanohablantes, puedes encontrarlo en Leanpub, en Amazon, y su sitio web de soporte, con todos los ejemplos de código.

Y sí, se habla C++.

Posted in software | Tagged , , | 1 Comment

C++17 – La guía completa

Updated on February 10, 2020
The following are the links at Amazon:
Amazon Spain
Amazon Mexico
Amazon USA
Amazon Canada

As an engineering student at my beloved alma mater, the Instituto Tecnológico de Chihuahua, I remember clearly during one of our electronics classes Professor Nevárez giving us the material, available at the school library…in English. I also remember clearly those thick, yellowish books, with a bunch of circuit drawings written in a language that, while I could read, it was difficult to grasp all the meaning.

Alas, I spoke little English at the time, having learned it through my middle school, high school, my summer visits to grandma and aunts, translating songs, reading the occasional book, and whatever other means I had at my disposal, but never did I attempt to read a technical book.

Nonetheless, I had an advantage in the sense that the effort for me to understand the material would be less. But it didn’t have to be that way if there had been material readily available in Spanish, but there wasn’t–at least not for that particular class.

Drawing from that experience, I got in touch with Nico. I had been providing him with feedback on his book, C++17 – The Complete Guide, and pitched the idea of translating it to Spanish. As I outlined to Nico that day, my main purpose was and still is to bring a quality C++ book to Spanish-speaking students and professionals.

You can see (or rather, read) the results of that collaboration.

Working with Nico has been an enlightening experience. He is a professional in every extent of the word and always conducts himself in a professional manner. Furthermore, understanding the amount of work that it takes to take a book, even after translated, to a finished product is really an eye opener. In a chat I had with Rainer Grimm in CPPCON 2018 he mentioned that after Nico published a book, there was little material left out, or gaps to fill, if any–or words to that effect. Coming from Rainer, a renowned author, that’s a lot to say about a colleague. Having seen Nico’s work up close, I can only add myself to that comment.

Translating a book is a challenging task. Not only do you have to translate the words, but you have to do so accounting for style and technical terms, and with the different variations in Spanish, make sure that you don’t use terms that are specific to a country–e.g., Mexico. For that I have José Daniel García to thank for. José Daniel is also a member of the C++ Committee and patiently answered my questions on usage of technical terms, suggestions, and better flow.

I strongly believe that this book fills a gap in the material readily available for C++ in Spanish, and it is my hope that it assists others in learning this beautiful programming language that I have used since 1996 and over the years to program systems for the 9-1-1 public safety market, to save lives on a daily basis.

Should you be inclined to buy it or recommend it further to your Spanish-speaking amigos, you can find it on Leanpub, on Amazon, and its support website, with all of the code examples.

And yes, se habla C++.

Posted in software | Tagged , , | 1 Comment

A Conversion Story: Improving from_chars and to_chars in C++17

The Dome of The Pantheon, Rome

Note: You can get source code and examples on Github at https://github.com/ljestrada/charconvutils and watch my CPPCON2019 Lightning Challenge talk on YouTube here, and the Spanish version of this post here.

Nope, this is not about a religious experience, epiphany, change of faith or meeting my CMaker. Rather, it’s about C++.

C++17 sports two low-level character conversion functions,  std::from_chars and std::to_chars, but they have a usage model that can be easily improved using the power of templates.

from_chars and to_chars are appealing because they have a number of nice guarantees: locale-independent, non-allocating and non-throwing. Compare std::to_chars to std::to_string, which is locale-dependent and can throw bad_alloc. Saying that, the functions are in reality sets of overloaded functions for integer types, char, and floating point types (float, double and long double). Notice that bool is not supported, and neither are the other character types.

Nicolai Josuttis has excellent coverage and devotes a full chapter to these functions in his C++17: The Complete Guide eBook. There you can find examples for from_chars and to_chars using structured bindings and if with initialization, two other new additions to C++17. Moreover, you can reference the documentation at cppreference.com for a few examples.

The function signatures for the character conversion functions from_chars are as follows:

std::from_chars_result from_chars(
  const char* first, const char* last, /*see below*/& value,
  int base = 10);

std::from_chars_result from_chars(
  const char* first, const char* last, float& value, 
  std::chars_format fmt = std::chars_format::general);

std::from_chars_result from_chars(
  const char* first, const char* last, double& value,
  std::chars_format fmt = std::chars_format::general);

std::from_chars_result from_chars(
  const char* first, const char* last, long double& value,
  std::chars_format fmt = std::chars_format::general);

struct from_chars_result {
    const char* ptr;
    std::errc ec;
};  

The first signature is for integer types. The /*see below*/ comment refers to the integer types that are supported, as well as char (again, but not bool or any of the other character types). The rest of the signatures are for floating point types. The structure from_chars_result is used to hold the result. Albeit the function signatures are not marked with noexcept, the documentation states that they do not throw–errors are communicated via the ec member in the from_chars_result structure. Anecdotally, Microsoft’s implementation “strengthens” the functions by marking them as noexcept.

Similarly, the function signatures for the character conversion functions to_chars are as follows:

std::to_chars_result to_chars(
  char* first, char* last, /*see below*/ value, int base = 10);

std::to_chars_result to_chars(char* first, char* last,
  float value);

std::to_chars_result to_chars(char* first, char* last,
  double value);

std::to_chars_result to_chars(char* first, char* last,
  long double value);

std::to_chars_result to_chars(char* first, char* last,
  float value, std::chars_format fmt);

std::to_chars_result to_chars(char* first, char* last,
  double value, std::chars_format fmt);

std::to_chars_result to_chars(char* first, char* last,
  long double value, std::chars_format fmt);

std::to_chars_result to_chars(char* first, char* last,
float value, std::chars_format fmt, int precision);

std::to_chars_result to_chars(char* first, char* last,
  double value, std::chars_format fmt, int precision);

std::to_chars_result to_chars(char* first, char* last,
long double value, std::chars_format fmt, int precision);

struct to_chars_result {
    char* ptr;
    std::errc ec;
}; 

The usage model requires that you always provide two pointers to char. Consider converting a character sequence to a floating point type using from_chars:

#include <charconv>

int main() {
  char a[] = "3.141592";
  float pi;
  std::from_chars_result res = std::from_chars(a, a+9, pi);
  if (res.ec != std::errc{} ) {
    // CONVERSION FAILED
  }
}

The overloaded function taking a float will be selected and the result will be placed in the variable pi.

Similarly, to turn a floating point variable into a character sequence using to_chars:

#include <charconv>

int main() {
  char a[10];
  float pi = 3.141592f;

  std::to_chars_result res = std::to_chars(a, a+10, pi);
  if (res.ec != std::errc{} ) {
    // CONVERSION FAILED
  }
}

See the documentation for the value of the ptr member upon success or failure.

While the usage model is consistent, one cannot help but notice that for cases where the array size is known, one must pass one pointer too many. What if we could use instead:

#include "charconvutils.hpp" // templates and pixie dust

using charconvutils::from_chars, charconvutils::to_chars;

int main() {
  char in[] = "3.141592";
  char out[50]; 
  float pi;

  // Look, ma, no tracking array size
  std::from_chars_result res1 = from_chars(in, value);
  std::to_chars_result   res2 = to_chars(out, pi);
}

The usage model is simplified: you do not need neither to keep track of the array size. Furthermore, you could use other classes such as std::array, std::string, std::string_view or std::vector. Interested? Read on.

Function templates to the rescue

By looking carefully at the function signatures, you can come up with the following observations:

  • All the functions take a pair [const] char* that constitute a valid range. In the case of from_chars, the range is read-only, in the case of to_chars, it is where the output will be placed.
  • The functions are overloaded for integer types, char and floating point types.

A first cut at a function template for from_chars could look like this (the ??? is information that will be provided later):

template<std::size_t N, typename T>
std::from_chars result
from_chars(const char(&a)[N], T& value, ???)
{
  return std::from_chars(a, a+N, value, ???);
}

This initial stub takes advantage of template argument deduction to determine the array size. Because the array is passed by reference (that’s what the syntax const char(&a)[N] does), it won’t decay onto a pointer, and that’s what we want.

Similarly, a first cut onto a function template for to_chars could look like this:

template<std::size_t N, typename T>
std::to_chars result
to_chars(const char(&a)[N], T& value, ???)
{
  return std::to_chars(a, a+N), value, ???);
}

Here’s a non-working version of the function templates using this approach, limited to from_chars for simplicity:

// For integral types
template<std::size_t N, typename T>
std::from_chars_result
from_chars(const char(&a)[N], T& value, int base = 10)
{
  return std::from_chars(a, a + N, value, base);
}

// For floating-point types
template<std::size_t N, typename T> 
std::from_chars_result
from_chars(const char(&a)[N], T& value,
           std::chars_format fmt = std::chars_format::general)
{
  return std::from_chars(a, a + N, value, fmt);
}

Running this version of the function templates somewhat works, but it breaks when you use a floating point type and don’t provide a base argument, defaulting to 10, and thinking that the second function template will be instantiated, but that is not the case. When called with the same number of arguments, the first function template will be instantiated with a float/double/long double, and the call to std::from_chars does not match.

Reversing the function template definitions–first the one for floating point types, then the one for integral types–does not work, either.

What we want is a way to instantiate the first function template when we pass an integer type, and the second when we pass a floating point type. What we need are templates and a little bit of pixie dust:

#include "charconvutils.hpp" // templates and pixie dust

 using charconvutils::from_chars, charconvutils::to_chars; 

int main() {
  char a[] = "365";
  int daysPerYear;

  // Instantiates a function template for integer types
  std::from_chars_result res1 = from_chars(a, daysPerYear);

  char b[] = "3.141592";
  float pi;

  // Instantiates a function template for floating point types
  std::from_chars_result res2 = from_chars(b, pi);
}

Of course, using the function with additional parameters would let the compiler pick the right function, too, such as providing a base for the integer types or fmt for the floating point types.

#include "charconvutils.hpp" // templates and pixie dust

 using charconvutils::from_chars, charconvutils::to_chars; 

 int main() {
  char a[] = "365";
  int daysPerYear;

  // Instantiates a function template for integer types
  std::from_chars_result res1 = from_chars(a, daysPerYear, 10);

  char b[] = "3.141592";
  float pi;

  // Instantiates a function template for floating point types
  std::from_chars_result res2 = from_chars(b, pi);
}

Enter enable_if

The solution is to use the type trait enable_if, available in the <type_traits> header file, or rather its alias template enable_if_t. This type trait yields void when passed one argument, or a given type when passed two arguments and the first argument yields true.

The pixie dust behind enable_if is that if the instantiation fails, the template in question will not be instantiated. Nifty. By using two other type traits, is_integral and is_floating_point, we define two helper alias templates that will let us discriminate between integral types and floating point types and select the proper template function. A minor caveat is that the is_integral includes bool and character types other than char, but for our purposes it should suffice–we can let the compiler reject other instantiations. Moreover, we will use the alias templates is_integral_v and is_floating_point_v, available since C++17, for simplification.

// Alias template for integer types types
template<typename T>
using EnableIfIntegral = 
  std::enable_if_t<std::is_integral_v<T>>;

// Alias template for floating point types
template<typename T>
using EnableIfFloating =
  std::enable_if_t<std::is_floating_point_v<T>>;


NOTE: Integer or integral? The type trait std::is_integral encompasses bool, character types and integer types. The std::from_chars and std::to_chars character conversion functions consider only integer types, char and floating point types. Keep in mind that the alias template EnableIfIntegral will filter anything allowed by the std::is_integral template.

With the alias templates handy, we define two function templates for the from_chars character conversion function (we will extend to types other than arrays of characters later):

template<std::size_t N, typename T, typename=EnableIfIntegral<T>>
std::from_chars_result
from_chars(const char(&a)[N], T& value, int base = 10)
{
  return std::from_chars(a, a + N, value, base);
}

template<std::size_t N, typename T  typename=EnableIfFloating<T>>
std::from_chars_result
from_chars(const char(&a)[N], T& value,
   std::chars_format fmt = std::chars_format::general)
{
  return std::from_chars(a, a + N, value, fmt);
}

Similarly, we define three function templates for the to_chars character conversion function. Notice that the function templates for floating types do not have a default format or precision:

template<std::size_t N, typename T, typename=EnableIfIntegral<T>>
std::to_chars_result
to_chars(char(&a)[N], T value, int base = 10)
{
  return std::to_chars(a, a + N), value, base);
}

template<std::size_t N, typename T, typename=EnableIfFloating<T>> 
std::to_chars_result
to_chars(char(&a)[N], T value, std::chars_format fmt)
{
  return std::to_chars(a, a + N), value, fmt);
}

template<std::size_t N, typename T, typename=EnableIfFloating<T>>  
std::to_chars_result
to_chars(char(&a)[N], T value, std::chars_format fmt,
         int precision)
{
    return std::to_chars(a, a + N), value, fmt, precision);
}

Extending to std::array

It is easy to extend the functionality to std::array. The corresponding from_chars and to_chars for std::array are straightforward. Here are a couple of examples for integer types:

template<std::size_t N, typename T, typename=EnableIfIntegral<T>>
std::from_chars_result
from_chars(const std::array<char, N>& a, T& value, int base = 10)
{
  return std::from_chars(a.data(), a.data() + N, value, base);
}

template<std::size_t N, typename T, typename=EnableIfIntegral<T>> 
std::to_chars_result
to_chars(std::array<char, N>& a, T value, int base = 10)
{
  return std::to_chars( a.data(), a.data() + N, value, base);
}

Extending to Random Access Containers

In Generic Programming and the STL, Matt Austern describes random access containers. The book relies on the SGI STL documentation (or vice versa) and Martin Broadhurst kindly preserved it here. You can find the description of random access containers here. With that said, std::string, std::vector, std::deque and std::array are random access containers and could be used as the source or destination of from_chars and to_chars. Simply allocate the storage needed for the conversion ahead of time. std::string_view, although strictly speaking not a random access container, qualifies because it provides the same interface, accessing its underlying std::string. Of course, one must guarantee that the containers won’t change while the conversion takes place.

With the definitions that follow, the function templates for std::array defined above can be removed and replaced.

The function template calculates the range at runtime using data() and size(). In addition, there’s a check to avoid passing containers whose value type is not char. Here are a couple of example for integer types:

template<typename Cont, typename T, typename=EnableIfIntegral<T>>
std::from_chars_result
from_chars(const Cont& c, T& value, int base = 10)
{
  static_assert(std::is_same_v<char, typename Cont::value_type>,
                "Container value type must be char.");
  return std::from_chars(c.data(), c.data() + c.size(), 
                         value, base);
}

template<typename Cont, typename T, typename=EnableIfIntegral<T>> 
std::to_chars_result
to_chars(Cont& c, T value, int base = 10)
{
  static_assert(std::is_same_v<char, typename Cont::value_type>,
                "Container value type must be char.");
  return std::to_chars(c.data(), c.data() + c.size(),
                       value, base);
}

Kicking the tires…

You would expect that after all of this template brouhaha things would just magically work, but alas, the compiler support for C++17 is, let’s say, unbalanced? I ran into different problems and decided to see if support for these character conversion functions was really there. I ran two programs with Compiler Explorer, one to test from_chars, which you can find here, and one to test to_chars, which you can find here.

I tested with C++ Compiler Explorer using the latest versions of MSVC, Clang and GCC, which support for C++17 (and therefore <charconv>).

For MSVC I used the flags /std:c++17, for Clang I used the flags -std=c++17 -stdlib=libc++ and for GCC I used the flags -std=c++17. If you run Clang with this flag effectively you’re using Clang as a front end to GCC.

The test program for from_chars is reproduced here. I’ve used the problematic portions with numbers (1 through 4). These tests are not exhaustive and you may try other types (e.g., wchar_t) to see if there are other inconsistencies in the support.

#include <charconv>

using std::from_chars, std::from_chars_result, 
      std::chars_format;

int main() {
  char const bstr[] = "true";
  bool bValue;

  // 1: bool should not be supported
  from_chars_result bres = from_chars(bstr, bstr+5, bValue);

  char const istr[] = "12345";
  int iValue;
  from_chars_result ires = from_chars(istr, istr+6, iValue);

  char const lstr[] = "12345";
  long lValue;
  from_chars_result lres = from_chars(lstr, lstr+6, lValue);

  char const fstr[] = "3.141592";
  float fValue{};

  // 2: Should pick overload that takes float
  from_chars_result fres = from_chars(fstr, fstr+9, fValue);

  char const dstr[] = "3.14159265358979";
  double dValue;

  // 3: Should pick overload that takes double
  from_chars_result dres = from_chars(dstr, dstr+17, dValue,
                                      chars_format::general);

  char const ldstr[] = "1234567890.50";
  long double ldValue;
  
  // 4: Should pick overload that takes long double
  from_chars_result ldres = from_chars(ldstr, ldstr+14, ldValue);
}

The test program for to_chars is reproduced here.

#include <charconv>

using std::to_chars, std::to_chars_result, std::chars_format;

int main() {
  char bstr[10];
  bool bValue{true};

  // 1: bool should not be supported
  to_chars_result bres = to_chars(bstr, bstr+5, bValue);

  char istr[6];
  int iValue{12345};
  to_chars_result ires = to_chars(istr, istr+6, iValue);

  char lstr[6];
  long lValue{12345};
  to_chars_result lres = to_chars(lstr, lstr+6, lValue);

  char fstr[9];
  float fValue{3.141592f};

  // 2: Should pick overload that takes float, chars_format
  to_chars_result fres = to_chars(fstr, fstr+9, fValue,
                                  chars_format::general);

  char dstr[17];
  double dValue{3.14159265358979};

  // 3: Should pick overload that takes double, chars_format,
  //    precision
  to_chars_result dres = to_chars(dstr, dstr+17, dValue,
                                  chars_format::general, 10);

  char ldstr[14];
  long double ldValue{1234567890.50};
  
  // 4: Should pick overload that takes long double, chars_format 
  to_chars_result ldres = to_chars(ldstr, ldstr+14, ldValue,
                                   chars_format::general, 10);
} 

Testing charconv support in Clang 8.0.0

The first thing I found is that there seems to be a bug in Clang’s implementation when using libc++. It correctly flags the conversion to bool, on both tests, but incorrectly flags the calls using floating point types at #2, #3 and #4 .

Testing charconv support in GCC 9.1

Right off, GCC cannot find std::chars_format. Similar results when using Clang as a front end to GCC. std::chars_format seems to be in another header and most probably has not been ported fully. It correctly flags the conversion to bool at #1, but incorrectly flags the calls using floating point types at #2, #3 and #4.

Testing charconv support in MSVC 19.20

The best results. I used flags /std:c++17. It correctly flags the conversion to bool at #1 for the from_chars test, but allows conversion from bool to char[] for the to_chars test (Incidentally, Visual Studio 2019 v16.2 Preview 1 adds additional support for floating point to_chars overloads and the feature test macro __cpp_lib_to_chars, a welcome improvement).

…and taking it for a spin

With the compilers out of the way, we can now turn towards testing the function templates from_chars and to_chars. Here’s the full "charconvutils.hpp" file, which you can find in Compiler Explorer here:

#ifndef CHARCONVUTILS_HPP
#define CHARCONVUTILS_HPP

#include <charconv>
#include <type_traits>

namespace charconvutils {

// -----
// Helper alias templates

template<typename T>
using EnableIfIntegral = 
  std::enable_if_t<std::is_integral_v<T>>;

template<typename T>
using EnableIfFloating = 
std::enable_if_t<std::is_floating_point_v<T>>;

// -----
// from_chars function templates for C-style arrays

template<std::size_t N, typename T,
         typename =  EnableIfIntegral<T>>
std::from_chars_result
from_chars(const char(&a)[N], T& value, int base = 10) {
  return std::from_chars(a, a + N, value, base);
}

template<std::size_t N, typename T, 
         typename =  EnableIfFloating<T>>
std::from_chars_result
from_chars(const char(&a)[N], T& value,
           std::chars_format fmt = std::chars_format::general) {
  return std::from_chars(a, a + N, value, fmt);
}

// -----
// from_chars function templates for random access containers

template<typename Cont, typename T,
         typename = EnableIfIntegral<T>>
std::from_chars_result
from_chars(const Cont& c, T& value, int base = 10) {
  static_assert(std::is_same_v<char, typename Cont::value_type>, 
                "Container value type must be char.");
  return std::from_chars(c.data(), c.data() + c.size(),
                         value, base);
}

template<typename Cont, typename T,
         typename = EnableIfFloating<T>>
std::from_chars_result
from_chars(const Cont& c, T& value,
           std::chars_format fmt = std::chars_format::general) {
  static_assert(std::is_same_v<char, typename Cont::value_type>, 
                "Container value type must be char.");
  return std::from_chars(c.data(), c.data() + c.size(),
                         value, fmt);
}

// -----
// to_chars function templates for C-style arrays

template<std::size_t N, typename T,
         typename = EnableIfIntegral<T>>
std::to_chars_result
to_chars(char(&a)[N], T value, int base = 10) {
  return std::to_chars(a, a + N, value, base);
}

template<std::size_t N, typename T,
         typename = EnableIfFloating<T>>
std::to_chars_result
to_chars(char(&a)[N], T value, std::chars_format fmt) {
  return std::to_chars(a, a + N, value, fmt);
}

template<std::size_t N, typename T,
         typename = EnableIfFloating<T>>
std::to_chars_result
to_chars(char(&a)[N], T value, std::chars_format fmt,
         int precision) {
  return std::to_chars(a, a + N, value, fmt, precision);
}

// -----
// to_chars function templates for random access containers

template<typename Cont, typename T,
         typename = EnableIfIntegral<T>>
std::to_chars_result
to_chars(Cont& c, T value, int base = 10) {
  static_assert(std::is_same_v<char, typename Cont::value_type>, 
                "Container value type must be char.");
  return std::to_chars(c.data(), c.data() + c.size(),
                       value, base);
}

template<typename Cont, typename T,
         typename = EnableIfFloating<T>>
std::to_chars_result
to_chars(Cont& c, T value, std::chars_format fmt) {
  static_assert(std::is_same_v<char, typename Cont::value_type>, 
                "Container value type must be char.");
  return std::to_chars(c.data(), c.data() + c.size(),
                       value, fmt);
}

template<typename Cont, typename T,
         typename = EnableIfFloating<T>> 
std::to_chars_result
to_chars(Cont& c, T value, std::chars_format fmt, int precision) {
  static_assert(std::is_same_v<char, typename Cont::value_type>,
                "Container value type must be char.");
  return std::to_chars(c.data(), c.data() + c.size(),
                       value, fmt, precision);
}

} // namespace charconvutils

#endif // CHARCONVUTILS_HPP

With this file in place, you can run the sample programs outlined above.

Afternotes

Jens Maurer proposed the character conversion functions in this paper. It’s interesting to see the proposed interfaces, and I could not find out why templates were not considered (albeit std::string_view was). Perhaps in a different iteration of the paper this idea was rejected and I’m missing something (e.g., considerations for the containers not to be accessed by multiple threads while the conversion takes place).

What about structured bindings? Sure, you can use structured bindings to capture the resulting structure–as previously mentioned, Nicolai Josuttis has several examples.

Another aspect that seems to be missing is that if the functions as declared are non throwing, why not mark them with noexcept? That’s what Microsoft did, and it seems only logical. Perhaps a consideration is that the range would become invalid while performing the conversion?

In closing, this post explored how one can improve the usage model for the character conversion functions by investing in function templates.

And remember, vote for Pedro.

Updated on June 20, 2019

Posted a bug report to Microsoft for taking the bool parameter and they report that the behavior is as designed (that is, the bool is promoted to an integer). You can find the problem report here.

The approach taken by Clang is that they mark the with delete, so it does not allow calling std::to_chars by passing a bool.

While logical, this discrepancy among compilers is problematic–which implementation is correct?

The problem I see with allowing the conversion to chars from bool is that there is no symmetry: Calling std::from_chars with “true” or “false” does not result in an int 1 or 0, or a bool. Now, that’s understandable, since there can be many representations for true and false strings (e.g., “yes” and “no”), but the opposite should hold, too.

Updated on August 28, 2019

An issue has been entered in the LWG that will delete the std::to_chars overload for bool overload. It can be found here:
https://cplusplus.github.io/LWG/issue3266
and the corresponding documentation in cppreference.com reflects it.

Updated on September 16, 2019

Reflects comments and simplification by using the data() member for containers, and now preserving the same semantics as the std::from_chars and std::to_chars functions.

Posted in C++, C++17, software | Tagged , , , , | 1 Comment