2
votes

Pourquoi le compilateur de modèles C ++ ne peut-il pas déduire des types à partir des arguments de pointeur intelligent covariants?

J'ai le code suivant

https://godbolt.org/z/7d1arK

Foo(bptr);

Ma question est pourquoi le compilateur ne peut-il pas gérer la ligne?

#include <memory>

template <typename T>
class A {

};

template <typename T>
class B : public A<T> {};


template <typename T>
void Foo(std::shared_ptr<A<T>> arg){}


int main(){
    auto bptr = std::make_shared<B<int>>();
    std::shared_ptr<A<int>> aptr = bptr;


    // This compiles
    Foo(aptr);

    // This does not compile
    Foo(bptr);

    // This compiles
    Foo<int>(bptr);
}


1 commentaires

Je dirais qu'un appel à une fonction de modèle entraîne une déduction de modèle en tant que premier ordre du jour. gcc signale un échec de déduction de modèle, et je suppose que regarder la superclasse et essayer de déduire les paramètres de modèle de la superclasse n'est tout simplement pas dans les cartes. Ce n'est pas un contexte déductible.


3 Réponses :


1
votes

Comme @SamVarshavchik et @aschepler l'ont mentionné, la déduction de modèle a lieu sans tenir compte des conversions définies par l'utilisateur, qui seraient nécessaires pour convertir de std::shared_ptr<B<int>> en std::shared_ptr<A<int>> <A <int> std::shared_ptr<A<int>> . Par conséquent, aucune instanciation appropriée du modèle n'est trouvée et l'appel échoue.

Une façon d'activer cela serait d'utiliser une gamme plus large de modèles, avec une assertion statique pour la réduire à l'activation uniquement des classes dérivées réelles de A :

#include <memory>
#include <type_traits>

template <typename T>
class A { };

template <typename T>
class B : public A<T> { };

class C { };


template <template<class> class Derived, class T>
void Foo(std::shared_ptr<Derived<T>> arg) {
    static_assert(std::is_base_of_v<A<T>, Derived<T>>);
}


int main() {
    auto bptr = std::make_shared<B<int>>();
    std::shared_ptr<A<int>> aptr = bptr;
    auto cptr = std::make_shared<C>();


    // This compiles
    Foo(aptr);

    // This compiles
    Foo(bptr);

    // This does not compile
    Foo(cptr);
}

Dans ce cas, Foo pourra accepter comme argument un std::shared_ptr à tout ce qui hérite de A


1 commentaires

La déduction des arguments de modèle peut examiner les classes de base, si aucune conversion définie par l'utilisateur n'est impliquée. template <class T> void f(A<T>*); peut déduire T=int d'un argument de type B<int>* .



0
votes

Comme @SamVarshavchik l'a dit, les conversions implicites définies par l'utilisateur ne sont pas autorisées dans la déduction des arguments de modèle. bptr est de type std::shared_ptr<B<int> > bptr qui est implicitement convertible en std::shared_ptr<A<int> > bptr std::shared_ptr<A<int> > puisque B<int>* est implicitement convertible en A<int>* , mais le modèle attend quelque chose comme std::shared_ptr<A<T> > directement.


2 commentaires

Certaines conversions implicites sont autorisées. Ce sont des conversions définies par l'utilisateur qui ne le sont pas.


@aschepler Merci, corrigé.



3
votes

La déduction des arguments de modèle et les conversions définies par l'utilisateur ne se mélangent pas. Parce qu'ils sont liés par une conversion définie par l'utilisateur, shared_ptr<A<int>> et shared_ptr<B<int>> ne sont pas "covariants" dans le même sens que des types comme A<int>* et B<int>* pour objectifs du langage C ++.

Bien que cela soit suffisant pour rendre l'appel illégal, ce cas est encore plus difficile car il n'y a pas un simple constructeur de conversion défini par l'utilisateur ou une fonction de conversion impliquée ici, mais un modèle de constructeur:

template <class T> class std::shared_ptr {
public:
    template <class Y>
    shared_ptr(const shared_ptr<Y>&);
    // ...
};

Donc, ici, le compilateur aurait besoin de déduire à la fois T et Y pour déterminer T "Evidemment" Y en déduira int , mais le cas général de cette complication n'est pas pratique à résoudre.

Et en fait dans ce cas, int n'est pas la seule valeur possible de T L'appel Foo<const int>(bptr) serait également légal.

Le langage C ++ spécifie exactement quand la déduction des arguments de modèle réussira et ne réussira pas, et avec quels arguments de modèle résultants, afin que nous puissions être certains que le code travaillant sur un compilateur conforme n'échouera pas sur un autre, du moins pas pour cette raison. À un moment donné, la ligne doit être dessinée, et l'une de ces lignes est "les conversions définies par l'utilisateur ne sont pas prises en compte lors de la déduction des arguments de modèle".


0 commentaires