2017-10-11 4 views
3

Vu le code suivantErreur dans le modèle instanciation avant la surcharge

#include <type_traits> 
#include <utility> 

template <typename T> 
class Something { 
public: 
    template <typename F> 
    auto foo(F&&) 
     -> decltype(std::declval<F>()(std::declval<T&>())) {} 
    template <typename F> 
    auto foo(F&&) const 
     -> decltype(std::declval<F>()(std::declval<const T&>())) {} 
}; 

int main() { 
    auto something = Something<int>{}; 
    something.foo([](auto& val) { 
     ++val; 
    }); 
} 

https://wandbox.org/permlink/j24Pe9qOXV0oHcA8

Lorsque je tente de compiler ce que je reçois l'erreur en disant que je ne suis pas autorisé à modifier une valeur const dans le lambda principale. Cela signifie que d'une manière ou d'une autre les templates sont tous deux instanciés dans la classe et que cela provoque une erreur dure puisque l'erreur est dans le corps du lambda.

Quelles sont les règles à ce sujet? Pourquoi la résolution de surcharge essaie-t-elle d'instancier un modèle qui ne sera jamais appelé? Le const ne devrait jamais être appelé ici alors pourquoi essaie-t-il de l'instancier complètement?

Cependant, chose étrange est que lorsque je change les définitions à retourner par decltype(auto) et ajouter le code pour faire la même chose que les types de retour à la traîne suggèrent, je ne vois pas une erreur. Indique que les modèles ne sont pas complètement instanciés?

template <typename F> 
decltype(auto) foo(F&& f) { 
    auto t = T{}; 
    f(t); 
} 
template <typename F> 
decltype(auto) foo(F&& f) const { 
    const auto t = T{}; 
    f(t); 
} 

Je suppose que le compilateur ne sait pas quelle fonction à appeler avant l'instanciation au moins la signature avec la fonction passée. Mais cela n'explique pas pourquoi la decltype la version (automatique) fonctionne ...

Répondre

4

(Toutes mes excuses pour l'absence de terminologie standard correcte, travaille ...)

Lorsque something.foo est invoqué, toutes les éventuelles surcharges doivent être pris en considération:

template <typename F> 
auto foo(F&&) 
    -> decltype(std::declval<F>()(std::declval<T&>())) {} 

template <typename F> 
auto foo(F&&) const 
    -> decltype(std::declval<F>()(std::declval<const T&>())) {} 

afin de vérifier si une surcharge est viable, les decltype(...) besoins de suivi à évaluer par le compilateur. Le premier decltype sera évalué sans erreur et il sera évalué à void. Le deuxième provoquera une erreur, car vous essayez d'appeler le lambda avec const T&.

Puisque le lambda n'est pas contraint, l'erreur se produira pendant l'instanciation du corps de lambda. Cela se produit car (par défaut) lambdas utilise la déduction de type de retour automatique, ce qui nécessite l'instanciation du corps de lambda. Par conséquent, la surcharge non viable provoquera donc une erreur de compilation au lieu de sortir SFINAEd. Si vous contraignez lambda comme suit ...

something.foo([](auto& val) -> decltype(++val, void()) { 
    ++val; 
}); 

... aucune erreur se produit, comme la surcharge sera considérée comme non viable par SFINAE. En outre, vous serez en mesure de détecter si l'invocation lambda est valide pour un type particulier (c'est-à-dire T prend en charge operator++()?) de Something::foo.


Lorsque vous changez le type de retour à decltype(auto), le type de retour est déduit du corps de la fonction.

template <typename F> 
decltype(auto) foo(F&& f) { 
    auto t = T{}; 
    f(t); 
} 

template <typename F> 
decltype(auto) foo(F&& f) const { 
    const auto t = T{}; 
    f(t); 
} 

Comme votre something exemple est non const, la surcharge const non qualifiée sera prise ici. Si votre main a été défini comme suit:

int main() { 
    const auto something = Something<int>{}; 
    something.foo([](auto& val) { 
     ++val; 
    }); 
} 

Vous obtiendrez la même erreur, même avec decltype(auto).

+0

"* l'erreur se produira pendant l'instanciation *" par opposition à quand? La substitution est-elle son propre pas? –

+1

@RyanHaining: lors de l'étape de substitution, le type de retour arrière decltype (...) 'instancie le corps de lambda ... ce qui provoque une erreur. –

+0

Quel est le moyen suggéré pour éviter ce problème ici? Il suffit de passer à 'decltype (auto)'? Je voudrais que la fonction 'foo' soit compatible SFINAE avec le type de retour et aussi que le code utilisateur fonctionne – Curious

1

En fait, je pense que l'essentiel du problème est que

Seuls les types et expressions non valides dans le contexte immédiat du type de fonction et de ses types de paramètres de modèle peuvent entraîner une défaillance de déduction. [Note: La substitution en types et expressions peut entraîner des effets tels que l'instanciation de spécialisations de modèle de classe et/ou de spécialisation de modèle de fonction, la génération de fonctions implicitement définies, etc. Ces effets ne sont pas dans le "contexte immédiat" et peut entraîner la formation du programme. - note de fin]

Ainsi, la question est de savoir si l'instanciation d'un lambda déclenchée lors de la déduction de son type de retour est considérée dans son contexte immédiat?

par exemple, si le type de retour lambda est explicite:

something.foo([](auto& val) -> void { 
    ++val; 
}); 

les compiles de code (pas sfinae, il est juste que le non const est une meilleure correspondance).

mais, le lambda OP a déduction automatique du type de retour, par conséquent le lambda est instancié et la règle susmentionnée s'applique.

+0

Cela a du sens, mais notez que le lambda n'est plus "SFINAE-friendly". Vous pouvez vérifier si '++ val' est une opération valide pour' val' de 'Something :: foo'. Si vous venez de dire '-> void', cela pourrait ressembler à une opération valide même si ce n'est pas le cas, produisant une erreur dure comme dans l'exemple de l'OP. –

+0

@VittorioRomeo ... uhm, mais il ne donne pas une erreur dure avec le code OP, c'est le point ... –

+0

c'est ce que je trouve problématique: https://wandbox.org/permlink/g7fNJgknnVXj7vrt - Je voudrais veulent '/ * fallback * /' y être choisis, mais '-> void' choisit une autre surcharge et ** provoque une erreur matérielle **.Correctement contraignant le lambda choisirait le repli. –