Informaticienzero

Le blog d'un informaticien passionné de partage, d'échanges et surtout, pas si zéro que ça.

Enfin ça ne sera pas disponible avec le standard de 2017 (malheureusement !), mais on peut déjà faire mu-muse avec, notamment avec GCC 7 et Clang 4, que l’on peut retrouver sur Wandbox. Il faudra simplement compiler avec l’option -fconcepts.

Les joies des templates

Pour bien comprendre pourquoi les concepts sont si intéressants en C++, il suffit de s’être mangé une erreur de templates dans les dents au moins une fois. Prenons cet exemple tiré de Stack Overflow.

#include <vector>
#include <algorithm>

int main()
{
	int a;
	std::vector<std::vector<int>> v;
	auto it = std::find(v.begin(), v.end(), a);
}

L’erreur, postée sur SO, vient du simple fait que l’objet a n’est pas du type attendu (std::vector<int>). Et les messages à rallonges n’aident pas à comprendre, tant chez GCC que chez Clang.

Les concepts : une TS pleine d’espoir

Évidemment, il se peut que l’implémentation, la syntaxe, etc changent, ou bien que de nouvelles choses soient ajoutées, puisque ce n’est qu’une spécification technique et absolument pas quelque chose de définitif. Mais on peut faire déjà des choses intéressantes, comme le code ci-dessous.

#include <algorithm>
#include <iostream>
#include <type_traits>

template<typename T>
concept bool Integral() 
{
	return std::is_integral<T>::value;
}

template<typename T>
concept bool StructWithInteger = requires(T param)
{
	param.i;
};

template<typename T>
concept bool Structure()
{
	return StructWithInteger<T> && Integral<decltype(T::i)>();
}

template <Structure T>
void f(T param)
{
	std::cout << "T::i == " << param.i << std::endl;
}

struct A
{
	int i = 42;
};

struct B
{
	int * i = nullptr;
};

struct C
{
	double i = 2.71818;
};

int main()
{
	f(A{});
	//f(B{}); // Isn't working and perfectly normal, because (int*) is not (int).
	//f(C{}); // Isn't working and perfectly normal, because (double) isn't (int).

	return 0;
}

Dans ce code, je décide que je ne veux en argument qu’une structure contenant un entier i. Si je décide de passer un pointeur, par exemple, voilà ce que j’obtiens comme erreur.

prog.cc: In function 'int main()':
prog.cc:47:17: error: cannot call function 'void f(T) [with T = B]'
	 f(B{}); // Isn't working and perfectly normal, because (int*) is not (int).
				 ^
prog.cc:24:6: note:   constraints not satisfied
 void f(T param)
	  ^~~~~~~~
prog.cc:18:14: note: within 'template<class T> concept bool Structure() [with T = B]'
 concept bool Structure()
			  ^~~~~~~~~
prog.cc:6:14: note: within 'template<class T> concept bool Integral() [with T = int*]'
 concept bool Integral()
			  ^~~~~~~~
prog.cc:6:14: note: 'std::is_integral<int*>::value' evaluated to false

Eh oui ! un pointeur n’est pas un entier, tout simplement. Et si je passe un entier mais qui n’a pas le bon identifiant ?

prog.cc: In function 'int main()':
prog.cc:46:17: error: cannot call function 'void f(T) [with T = A]'
	 f(A{});
				 ^
prog.cc:24:6: note:   constraints not satisfied
 void f(T param)
	  ^~~~~~~~
prog.cc:24:6: note: in the expansion of concept '(Structure<T>)()' template<class T> concept bool Structure() [with T = A]
prog.cc:20:36: error: 'i' is not a member of 'A'
	 return StructWithInteger<T> && Integral<decltype(T::i)>();
									^~~~~~~~~~~~~~~~~~~~~~~~

C’est donc vraiment quelque chose de puissant qui se dessine. On peut ainsi rêver d’avoir la puissances des templates C++ avec les contraintes comme en C#, pour un codage et des messages d’erreurs plus simples. Voici, en guise de conclusion, des liens vers tout un tas de ressources qui m’ont aidé à appréhender les concepts.