[C++0x] Ressuscitons les concepts avec « SFINAE for expressions »
Bonjour à tous,
Je fais suite à cette conversation dans la rubrique « Langage » étant donné que ça n'a plus rien à voir avec Boost.
Pour résumer :
Il existe la bibliothèque boost::concept_check qui permet de formaliser des concepts à partir d'un ensemble d'expressions. C'est pratique (pour avoir des messages d'erreurs plus explicites) et très expressif, mais ça ne permet pas de faire de surcharge. Il y a donc une grosse perte d'intérêt par rapport aux concepts initiaux de C++0x qui, je le rappelle, ont été abandonnés.
Il y a cependant un espoir : il sera possible (et même est possible avec GCC 4.4 et peut-être d'autres compilateurs récents) d'exploiter le mécanisme SFINAE pour les expressions. Il faut bien sûr que l'expression apparaisse dans la signature de la fonction à surcharger ; pour ce faire on utilise decltype, également nouvelle fonctionnalité de C++0x.
Rien de plus clair qu'un petit exemple.
On a une classe X et un classe Y.
Il est possible de multiplier des X entre eux, mais pas des Y.
Il est possible d'additionner des Y entre eux, mais pas des X.
On souhaite surcharger le template de fonction f :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
#include <iostream>
struct X
{
X(int value):
value_(value)
{
}
int value_;
};
X
operator*(X x1, X x2)
{
return X(x1.value_ * x2.value_);
}
struct Y
{
Y(int value):
value_(value)
{
}
int value_;
};
Y
operator+(Y y1, Y y2)
{
return Y(y1.value_ + y2.value_);
}
template<class T>
auto
f(T t1, T t2) -> decltype(t1 + t2) // #1
{
std::cout << "#1\n";
return t1 + t2;
}
template<class T>
auto
f(T t1, T t2) -> decltype(t1 * t2) // #2
{
std::cout << "#2\n";
return t1 * t2;
}
int main()
{
X x1(0), x2(0);
X x3 = f(x1, x2); // deduction fails on #1 (cannot add X+X), calls #2
Y y1(0), y2(0);
Y y3 = f(y1, y2); // calls #1
} |
Il est tout à fait possible d'utiliser le mécanisme avec des signatures de fonction classiques (c'est-à-dire n'ayant pas la forme « auto f() -> decltype »), en utilisant par exemple un « paramètre fantôme » (comme on peut le faire avec boost::enable_if) :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
template<typename>
class require
{
public:
typedef void* type;
};
//#1
template<class T>
T
f(T t1, T t2, typename require<decltype(t1 * t2)>::type = 0)
{
std::cout << "#1\n";
return t1 * t2;
}
//#2
template<class T>
T
f(T t1, T t2, typename require<decltype(t1 + t2)>::type = 0)
{
std::cout << "#2\n";
return t1 + t2;
}
int main()
{
X x1(0), x2(0);
Y y1(0), y2(0);
X x3 = f(x1, x2); //calls #1
Y y3 = f(y1, y2); //calls #2
} |
Pensez-vous qu'à partir de cette piste, il soit possible d'externaliser les expressions et de les regrouper dans une même classe qui serait alors une classe « concept » (de la même façon qu'avec boost::concept_check, pour ceux qui connaissent) ?
Au fond de moi je me dis « si c'était possible, ça se saurait, un gourou en aurait déjà parlé au moment du tollé lors de l'abandon des concepts », mais je tente tout de même le coup ;).