std::forward()
n'a rien à voir avec cette question. Pas plus constexpr
. Commençons donc avec le super base, une fonction qui renvoie simplement son argument en valeur:
template <class T>
auto f(T x) -> decltype(x);
Bien sûr, nous pourrions simplement utiliser T
, mais c'est trop facile. Maintenant, la question, comment pouvons-nous élevons que dans un paramètre de modèle tout en conservant SFINAE (Accordée il n'y a évidemment pas d'échec de substitution possible, mais garder avec moi):
template <class T, class R = decltype(x)>
R f(T x);
Donc cela ne fonctionne pas parce qu'il n'y a pas x
encore (ou pire, il y a des non apparentés quelque part d'autre que la recherche de nom trouve). Nous pouvons utiliser les noms d'arguments dans un type de retour-retour parce qu'ils sont dans la portée, mais nous ne pouvons pas les utiliser dans le cadre d'expression-SFINAE dans les arguments de modèle par défaut car ils ne sont pas encore dans la portée.
Mais parce que ce sont des paramètres de modèle, nous ne nous soucions pas de leurs valeurs. Nous nous soucions seulement de leurs types. Ce n'est pas une expression évaluée. Donc je n'ai pas besoin de x
, j'ai besoin de quelque chose qui a le même type que x
. Un premier rendez-vous pourrait être:
template <class T, class R = decltype(T{})>
R f(T x);
Cela fonctionne ... tant que T
est par défaut constructible. Mais j'écris un modèle, je ne veux pas faire d'hypothèses sur les types que je n'ai pas besoin de faire.Ainsi, au lieu, je peux faire quelque chose comme:
template <class T> T make(); // never defined
template <class T, class R = decltype(make<T>())>
R f(T x);
Et maintenant nous avons notre expression arbitraire de type T
, que nous pouvons utiliser dans decltype
, dans un argument de modèle par défaut. Bien que, nous sommes encore un peu limité avec make()
- il y a des types que vous ne pouvez pas renvoyer par valeur d'une foncton (par exemple des tableaux), ainsi il s'avère plus utile d'ajouter des références. Et nous avons besoin d'un moins probable à Collide nom que make
:
template <class T>
add_rvalue_reference<T> declval();
template <class T, class R = decltype(declval<T>())>
R f(T x);
C'est précisément le point de std::declval<T>
- pour vous donner une expression de type T
dans un contexte non évalué.
Retour à votre problème d'origine. Nous utilisons le même processus de réflexion pour savoir comment passer de decltype(x)
à decltype(declval<T>())
, mais il suffit de l'appliquer à une expression différente. Au lieu de x
, nous avons myfunction(std::forward<T>(x))
. Ce qui veut dire, nous invoquer myfunction
avec le même type que notre argument:
template <class T, class R = decltype(myfunction(std::declval<T&&>()))>
R f(T&& x);
Mais en raison de règles de référence effondrement, std::declval<T&&>
est en fait la même fonction que std::declval<T>
, donc nous pouvons simplement écrire:
template <class T, class R = decltype(myfunction(std::declval<T>()))>
R f(T&& x);
Je mentionnerais que c'est une mauvaise approche, car on peut facilement la casser en passant un second paramètre. – skypjack
@skypjack: Droit, évitant ainsi toute SFINAE ayant lieu. – ildjarn
Oui, en disant _break it_, je voulais dire ça. Désolé si ce n'était pas clair. ;-) – skypjack