2

Considérons la fonction suivante:obtenir le type transmis par le type de modèle et non l'argument

template <class T> 
constexpr /* something */ f(T&& x) { 
    // do something 
} 

et disons que je veux faire sfinae en fonction du type d'un argument transmis est passé à une fonction appelée myfunction. Une façon d'y parvenir est:

template <class T> 
constexpr auto f(T&& x) -> decltype(myfunction(std::forward<T>(x))) { 
    // do something 
} 

au lieu de le faire, est-il un moyen de le faire au niveau du modèle:

// This code won't compile 
template <class T, class R = decltype(myfunction(std::forward<T>(x)))> 
constexpr R f(T&& x) { 
    // do something 
} 

sauf que je n'ai pas accès à x encore si ce code ne compilera pas. Existe-t-il un moyen d'atteindre cet objectif uniquement en se basant sur T (en utilisant éventuellement std::declval)? Remarque: ce n'est pas un problème X/Y, c'est juste un exemple pour illustrer où cette situation se produit: Je ne sais pas comment faire SFINAE avec le transfert sans accéder à la variable car pour moi le comportement de std::forward est toujours un peu mystérieux.

Répondre

5

Oui, std::declval est en effet la clé:

template <class T, class R = decltype(myfunction(std::declval<T>()))> 
constexpr R f(T&& x) { 
    // do something 
} 

Ce sera soit SFINAE sur ou R sera le type de retour de surcharge selon des myfunction est choisi.

Votre question me donne l'impression que vous avez besoin d'un rappel sur le fonctionnement de la fonction de réduction de référence; Je suggère de lire dans cette zone (this semble être un bon point de départ).

+0

Je mentionnerais que c'est une mauvaise approche, car on peut facilement la casser en passant un second paramètre. – skypjack

+0

@skypjack: Droit, évitant ainsi toute SFINAE ayant lieu. – ildjarn

+0

Oui, en disant _break it_, je voulais dire ça. Désolé si ce n'était pas clair. ;-) – skypjack

3

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);