Archives d’Auteur

Let’s try C++20 | Range-based for statements with initializer

Thomas Köppe wrote the proposal P0614R1 to describe a new feature called "Range-based for statements with initializer". This document has been approved as part of the C++20 standard.

The document is pretty straight-forward since the feature is quite simple. If you have heard of if statement with initializer from C++17, then you have probable already guessed what "range-based for statements with initializer" means.

To understand this new feature, let’s say we want a code to print all the element in a collection. The first idea with have would be to use a range-based for loop. But let’s add another requirement: we want to print the index of the element in the collection. Let’s write some code before and after C++20 and compare them.

Before C++20

Using a range-based for loop, the code would look like this:

#include <iostream>
#include <array>

int main() {
    std::array data = {"hello", ",", "world"};

    std::size_t i = 0;
    for (auto& d : data) {
        std::cout << i++ << ' ' << d << '\n';
    }
}

Some people, including me, would argue that i‘s scope is too large. The variable is still available after the loop and this may not be desirable. Then they would backup to a code like this:

int main() {
    std::array data = {"hello", ",", "world"};

    for (std::size_t i = 0; i < data.size(); ++i) {
        std::cout << i << ' ' << data[i] << '\n';
    }
}

Some people, including me, would argue this code is more verbose, less generic (since not all collections has a size() member function), and has a less explicit intent.

After C++20

With C++20, we can now do this:

int main() {
    std::array data = {"hello", ",", "world"};

    for (std::size_t i = 0; auto& d : data) {
        std::cout << i++ << ' ' << d << '\n';
    }
}

Close to perfection 🙂

A more complex case

The proposal also talks about an undefined behavior that we are likely to run into as we try to reduce the scope of variables with range-based loops. Let’s consider this code:

#include <iostream>
#include <vector>

class Foo {
public:
    const auto& items() {
        return data;
    }

private:
    std::vector<const char*> data{"hello", ",", "world"};
};

Foo getFoo() {
    return Foo();
}

int main() {
    for (auto& d : getFoo().items()) {
        std::cout << d << '\n';
    }
}

Something is wrong with this code. Can you guess what?

getFoo().items() is a dangling reference.

We can run this code in cppinsight to understand how the loop is compiled:

const std::vector<const char*, std::allocator<const char*>>& __range1 = getFoo().items();

__gnu_cxx::__normal_iterator<const char* const*, std::vector<const char*, std::allocator<const char*>>> __begin1 = __range1.begin();
__gnu_cxx::__normal_iterator<const char* const*, std::vector<const char*, std::allocator<const char*>>> __end1 = __range1.end();

for (; __gnu_cxx::operator!=(__begin1, __end1); __begin1.operator++()) {
    const char* const& d = __begin1.operator*();
    std::operator<<(std::operator<<(std::cout, d), '\n');
}
  1. A reference to the vector is saved as __range1.
  2. Iterators are created from this reference.
  3. The loop is performed using these iterations.

We can see that the object returned by getFoo() and the vector it contains don’t survive the first line. Hence, __range1 is obviously a dangling reference.

I have to be honest: it took me some time to understand the problem here, so I guess I would have run in this UB in a real-file situation… 😒

With C++20, we can write this UB-free code with tight scope:

int main() {
    for (auto foo = getFoo(); auto& d : foo.items()) {
        std::cout << d << '\n';
    }
}

Conclusion

I thought with article would be simple because the addition of an init statement to range-based loop is easy understand. In the end, I have discovered a subtle and not-so-easy to get UB.


std::optional from C++17 vs custom type for optional value

C++17 has introduced a very useful template class: std::optional:

The class template std::optional manages an optional contained value, i.e. a value that may or may not be present.

I have been using this feature a lot since 2017, even for a simple optional integer. Today, I got curious: is this template type effective compared to an equivalent type that I would write myself to achieve the same behavior?

Good question, thank you 😆

Let’s try to write some code and use Compiler Explorer to compare both solutions: std::optional vs custom type for optional value.

First, let’s define two equivalent types:

#include <optional>

using StdOptionalInt = std::optional<int>;

struct CustomOptionalInt {    
    bool has_value;
    int value;
};

Then, let’s write two functions that test if the value is available and return either this value (if available) or a default value (if not available):

int getStdOptional(const StdOptionalInt& o) {
    return o.has_value() ? o.value() : 0;
}

int getCustomOptional(const CustomOptionalInt& o) {
    return o.has_value ? o.value : 0;
}

Finally, compile the code with Compiler Explorer and compare the output assembly codes (you can try by yourself here):

with_compiler_explorer

Yeah! 😃 The 2 functions generate the same assembly code. There is no difference of performance. Notice the static assertion at the end of the source code: because the code compiles, it means the footprint are the same. The behavior is the same with gcc and clang for x86-64.

As a conclusion, std::optional is as efficient as a custom type to represent an optional integer value. Don’t implement your own type, simply use the standard type. You may even get better performance using std::optional, as explained on cppreference:

As opposed to other approaches, such as std::pair<T,bool>, optional handles expensive-to-construct objects well and is more readable, as the intent is expressed explicitly.


By the way, if you happen to speak French:

  • I wrote an article explain to how to use std::optional.
  • I made a video to present Compiler Explorer.

Exécuter du code depuis Visual Studio Code

Avec un tel nom, il est évident que Visual Studio Code permet de lancer du code… mais comment faire pour exécuter mon fichier ?

Installer le plugin Code Runner

J’utilise depuis quelques temps le plugin Code Runner et il fait bien le taff. Il permet d’exécuter un snippet ou un fichier et la liste des langages supportés est longue comme le bras :

Run code snippet or code file for multiple languages: C, C++, Java, JavaScript, PHP, Python, Perl, Perl 6, Ruby, Go, Lua, Groovy, PowerShell, BAT/CMD, BASH/SH, F# Script, F# (.NET Core), C# Script, C# (.NET Core), VBScript, TypeScript, CoffeeScript, Scala, Swift, Julia, Crystal, OCaml Script, R, AppleScript, Elixir, Visual Basic .NET, Clojure, Haxe, Objective-C, Rust, Racket, Scheme, AutoHotkey, AutoIt, Kotlin, Dart, Free Pascal, Haskell, Nim, D, Lisp, Kit, V, and custom command

Rappel pour installer un plugin : dans la barre de gauche, il suffit cliquer sur le bouton des plugins dans la barre de gauche (c’est celui avec des carrés façon puzzle, hein) et de chercher le nom du plugin (ici code runner). Vous sélectionner votre plugin dans la liste et vous faites Install :

Exécuter du code

Voilà, c’est bon ! Vous pouvez exécuter du code ! La documentation nous dit qu’il y a plusieurs façons de faire :

To run code:

  • use shortcut Ctrl+Alt+N
  • or press F1 and then select/type Run Code,
  • or right click the Text Editor and then click Run Code in > editor context menu
  • or click Run Code button in editor title menu
  • or click Run Code button in context menu of file explorer

Exemple avec Python

Vous écrivez un fichier main.py, vous faites Crtl+Alt+N (ou une autre technique de votre choix) et c’est tout !

Vous vous demandez d’où vient cette commande python -u pour éxecuter mon fichier ? C’est tout simple : Code Runner a une liste de commandes pour les différents langages supportés et c’est comme ça qu’il réussit à exécuter mon fichier main.py.

A noter : il faut sélectionner votre fichier pour pouvoir l’exécuter (en cliquant dessus dans l’éditeur). Son extension est importante puisque Code Runner s’en sert pour déterminer le langage utilisé et donc comment l’exécuter.

Exemple avec C++

Essayons maintenant avec un petit main.cpp :

#include <iostream>

template <typename... Args>
void all_true(Args... args)
{
    auto ok = (args and ...);
    std::cout << (ok ? "OK" : "Nope") << '\n';
}

int main()
{
    all_true(true, 12 == 3 * 4, 6 * 111 / 66 == 11);
    all_true(42, 1 + 1 == 2);
}

Résultat :

[Running] cd "c:\Users\z19100018\Desktop\temp\article_visual_studio_code\"
          && g++ main.cpp -o main
          && "c:\Users\z19100018\Desktop\temp\article_visual_studio_code\"main
main.cpp: In function 'void all_true(Args ...)':
main.cpp:6:25: warning: fold-expressions only available with -std=c++1z or -std=gnu++1z
    auto ok = (args and ...);
                        ^~~
Nope
OK

[Done] exited with code=0 in 1.066 seconds

Ben oui : mon code utilise une fold expression de C++17, la commande par défaut pour le C++ ne précise pas le standard, et il est probable que mon MinGW64 n’utilise pas cette version du standard par défaut.

Accessoirement, je n’ai pas exactement envie de compiler sans les options -Wall -Wextra

Mais, alors ? Je fais quoi ? Et bien je modifie la commande par défaut !

Personnaliser les commandes de Code Runner

La commande par défaut peut ne pas vous convenir :

  • Pour Python, ça prend la version qui est dans le PATH mais je peux vouloir choisir d’utiliser la 3 plutôt que la 2.
  • Pour C++, je n’ai pas mes flags préférés.
  • Et ça sera peut-être / sans doute pareil pour d’autres langages…

La solution est d’utiliser de modifier le fichier settings.json de Visual Studio Code, le fichier magique qui contient les paramètres de Visual Studio Code. Appuyez sur F1 (la touche magique pour chercher partout), tapez « Settings » et choisissez « Preferences: Open Settings (JSON) » :

Ca ouvre votre settings.json et si vous n’avez pas encore rien configuré, il devrait juste contenir deux accolades : du JSON avec rien dedans.

Pour chaque langage que Code Runner supporte, vous pouvez ajouter une entrée dans code-runner.executorMap. Voici mon fichier avec ma commande pour C++ :

{
    "code-runner.executorMap": {
        "cpp": "cd $dir && g++ -std=c++17 -Wall -Wextra $fileName 
               -o $fileNameWithoutExt && $dir$fileNameWithoutExt"
    },
    "files.autoSave": "afterDelay"
}

Si vous vous demandez à quoi sert l’autre ligne, c’est simplement pour activer la sauvegarde automatique des fichiers. Oui, j’aime pas passer mon temps à faire Crtl+S.

Remarquez que la commande contient la variable $fileName : c’est pour ça qu’il faut sélectionner votre fichier pour pouvoir l’exécuter.

Si je refais Crtl+Alt+N sur mon fichier main.cpp, c’est évidemment bien mieux :

[Running] cd "c:\Users\z19100018\Desktop\temp\article_visual_studio_code\" 
         && g++ -std=c++17 -Wall -Wextra main.cpp -o main 
         && "c:\Users\z19100018\Desktop\temp\article_visual_studio_code\"main
Nope
OK

[Done] exited with code=0 in 1.291 seconds

Pour plus de détails

Je pense que l’essentiel a été dit. Pour plus de détails, je vous laisse parcourir la La documentation officielle.

Allez, faut que je vous laisse, j’ai du code à exécuter !


Google Sheets : faire des critères complexes pour SUMIF() et autres

Une fonction comme SUMIF (SOMME.SI si vous utilisez des formules en français) permet de faire une somme des éléments d’une plage répondant à un critère. On retrouve le même concept pour AVERAGEIF, SUMIFS ou encore COUNTIF. Sauf que quand on regarde la documentation, les critères semblent très basiques… Par exemple, si ont veut sommer les valeurs positives de la colonne A, la formule est SUMIF(A:A, ">0"). Si on veut sommer les lignes de la colonne A pour lesquelles les lignes de la colonne B sont « Validé », c’est SUMIF(B:B, "Validé", A:A). Mais comment faire si on souhaite appliquer une formule à une plage et utiliser le résultat pour valider le critère ?

Prenons cet exemple de données d’entrée :

Ce sont des simples opérations financières, avec une date et une valeur. Imaginons maintenant qu’on souhaite faire la somme des valeurs par mois. De manière intuitive, on se dit que pour chaque mois, il faut un SUMIF de la colonne B si le mois de la colonne A est celui du mois considéré. Par exemple, pour Janvier, on aurait envie de faire quelque chose comme SUMIF(MONTH(A:A), 1, B:B) mais ça ne marche pas. On se dit qu’il faudrait créer un range avec les mois de la colonne A, sans forcément créer une colonne pour ça (car oui, créer une colonne intermédiaire est une solution simple efficace). Bonne nouvelle, il existe la fonction qu’il nous faut ! C’est ARRAYFORMULA ! Si dans une cellule, vous mettez ARRAYFORMULA(DATE(A:A)), vous verrez la colonne se remplir avec les mois des dates. Il suffit donc d’injecter ça dans notre SUMIF.

Voici ce que ça pourrait donner :

La colonne D et E sont créées manuellement. La formule en pour la colonne F est SUMIF(ARRAYFORMULA(MONTH(A:A));D:D;B:B). L’utilisation de la colonne D (qu’on peut masquer, car il est vrai que visuellement c’est pas foufou) permet d’avoir la même formule dans toutes les cellules de la colonne F sans devoir incrémenter manuellement le numéro du mois dans la formule (le D:D remplace le numéro du mois). Essayez de le coder, vous verrez que c’est plus facile 😉


Google Apps Script : la valeur cachée de ItemType

Si vous faites joujou avec des Google Forms et du Apps Script, vous êtes sans doute tombé sur l’énumération ItemType. L’interface Item représente un élément d’un formulaire et il y a plusieurs classes qui répondent à cette interface, comme TextItem qui permet de saisir du texte ou DateItem qui permet de choisir une date. Sans juger de la qualité OO du design, il faut noter que Item possède une méthode getType() qui renvoie une valeur de l’énumération ItemType. Cela permet de caster ensuite un Item dans le « bon » type pour le traiter selon son type réel (je n’invente rien, ce mode opératoire est proposé au début de la description de l’interface).

Voici la liste des valeurs de l’énumération d’après sa documentation :

  1. CHECKBOX
  2. CHECKBOX_GRID
  3. DATE
  4. DATETIME
  5. DURATION
  6. GRID
  7. IMAGE
  8. LIST
  9. MULTIPLE_CHOICE
  10. PAGE_BREAK
  11. PARAGRAPH_TEXT
  12. SCALE
  13. SECTION_HEADER
  14. TEXT
  15. TIME
  16. VIDEO

Cela veut dire que la méthode ordinal() sur un membre de cette énumération renvoie une valeur entre 0 et 15.

Moi, j’ai un formulaire avec un champ pour uploader un fichier :

google forms

Je ne vois aucun type correspondant dans l’énumération, pas grave, je vais tester. J’installe un trigger pour exécuter le script suivant quand une réponse est postée. Que pensez-vous que je vais obtenir dans mes logs lors de la prochaine réponse ?

function onFormSubmitted(event) {
var itemResponse = event.response.getItemResponses()[0];
var title = itemResponse.getItem().getTitle();
var response = itemResponse.getResponse();
console.log(title + ' : ' + response);
var type = itemResponse.getItem().getType();
console.log(type);
console.log(type.toString());
console.log(type.ordinal());
}

Dans le journal des exécutions, je vois :

13 juin 2019 à 15:59:07 DÉBOGAGE Mon fichier : 1rTvMQfzkp3UgVn4aj4-BOlf_6s2rB4
13 juin 2019 à 15:59:07	DÉBOGAGE FILE_UPLOAD
13 juin 2019 à 15:59:07 DÉBOGAGE FILE_UPLOAD
13 juin 2019 à 15:59:07 DÉBOGAGE 16.0

« Surprise, motherf*cker!« 

Attends, je teste un truc ! J’exécute cette fonction :

function testEnum() {
  Logger.log(FormApp.ItemType.VIDEO);
  Logger.log(FormApp.ItemType.FILE_UPLOAD);
}

Que vois-je dans les journaux ?

[19-06-13 16:19:34:803 CEST] VIDEO
[19-06-13 16:19:34:803 CEST] undefined

Bim ! C’est ça qui est bon ! Que de fun !

Du coup, si dans ton script, tu veux savoir si tu traites un Item de type FILE_UPLOAD, et bien il faut ruser… Voici par exemple une fonction pour générer des liens vers les fichiers uploadés :

function onFormSubmitted(event) {
	var itemResponse = event.response.getItemResponses()[0];
	var response = itemResponse.getResponse();

	var type = itemResponse.getItem().getType();
	if (type == "FILE_UPLOAD") {
		console.log(response);
		console.log(typeof response);

		for(var u = 0; u < response.length; u++) {
			var url = 'https://drive.google.com/open?id=' + response[u];
			console.log('Uploaded file URL = ' + url);
		}
	}
}

On obtient dans les logs :

13 juin 2019 à 16:28:29 DÉBOGAGE [1tLkWu6ij1qcb3ZFeCl7ZTnRYKL1zK7]
13 juin 2019 à 16:28:29 DÉBOGAGE object
13 juin 2019 à 16:28:29 DÉBOGAGE Uploaded file URL = 
      https://drive.google.com/open?id=1tLkWu6ij1qcb3ZFeCl7ZTnRYKL1zK7

Ah oui ! N’oublions pas cette magie du JavaScript pour qui un tableau est de type Object. La réponse à un tel Item est bien un tableau des ID des fichiers téléchargés, même s’il n’y a qu’un seul fichier.