La fonction de modèle ne fonctionne pas pour la fonction pointeur-sur-membre prenant const ref

14

Dernièrement, j'ai écrit une fonction de modèle pour résoudre certaines répétitions de code. Cela ressemble à ceci:

template<class T, class R, class... Args>
R call_or_throw(const std::weak_ptr<T>& ptr, const std::string& error, R (T::*fun)(Args...), Args... args) {
    if (auto sp = ptr.lock()) 
    {
        return std::invoke(fun, *sp, args...);
    }
    else 
    {
        throw std::runtime_error(error.c_str());
    }
}

int main() {
    auto a = std::make_shared<A>();
    call_or_throw(std::weak_ptr<A>(a), "err", &A::foo, 1);
}

Ce code fonctionne parfaitement bien pour class Ace qui ressemble à ceci:

class A {
public:
    void foo(int x) {

    }
};

Mais ne parvient pas à compiler pour celui-ci:

class A {
public:
    void foo(const int& x) {

    }
};

Pourquoi en est-il ainsi (par pourquoi je veux dire pourquoi il ne parvient pas à déduire le type) et comment (si c'est possible) pour faire fonctionner ce code avec des références? Exemple en direct

bartop
la source
peut Args&&...- être et std::forward?
Fas
@ user3365922 l'a essayé. Se sent comme une solution, ne fonctionne pas
bartop
Ne serait - ce et cela vous aide dans la bonne direction?
Gizmo

Réponses:

3

Votre problème est que vous avez des déductions de conflit Argsentre:

  • R (T::*fun)(Args...)
  • Args... args

Je suggère d'avoir un code plus générique (pas de duplication entre R (T::*fun)(Args...)et la
version const R (T::*fun)(Args...) constet autre alternative) avec:

template<class T, class F, class... Args>
decltype(auto) call_or_throw(const std::weak_ptr<T>& ptr,
                             const std::string& error,
                             F f,
                             Args&&... args)
{
    if (auto sp = ptr.lock()) 
    {
        return std::invoke(f, *sp, std::forward<Args>(args)...);
    }
    else 
    {
        throw std::runtime_error(error.c_str());
    }
}
Jarod42
la source
bon point sur la qualification cv de la fonction membre, je pense que c'est la meilleure solution jusqu'à présent
bartop
8

Argsles types ne peuvent pas être déduits à la fois comme const&(de la fundéclaration de paramètre) et non-référence de la argsdéclaration. Une solution simple consiste à utiliser deux packs de paramètres de type de modèle distincts:

template<class T, class R, class... Args, class... DeclaredArgs>
R call_or_throw(
    const std::weak_ptr<T>& ptr,
    const std::string& error,
    R (T::*fun)(DeclaredArgs...),
    Args... args);

Comme inconvénient, je peux imaginer des messages d'erreur légèrement plus longs en cas de mauvaise utilisation.

LogicStuff
la source
1
Vous voulez probablementArgs&&... args
Jarod42
5

Notez que le type du paramètre Argsde modèle est déduit comme const int&sur le troisième argument de fonction &A::fooet déduit comme intsur le quatrième paramètre de fonction 1. Ils ne correspondent pas et la déduction échoue.

Vous pouvez exclure le 4ème paramètre de la déduction , par exemple

template<class T, class R, class... Args>
R call_or_throw(const std::weak_ptr<T>& ptr, 
                const std::string& error, 
                R (T::*fun)(Args...), 
                std::type_identity_t<Args>... args) {
//              ^^^^^^^^^^^^^^^^^^^^^^^^^^                

VIVRE

PS: std::type_identityest pris en charge depuis C ++ 20; mais il est assez facile d'en implémenter un.

songyuanyao
la source
1
est-ce que ça va fonctionner avec un transfert parfait d'une manière ou d'une autre?
bartop
@bartop Je pense que oui. Nous pouvons rendre le 4ème paramètre conforme au style de référence de transfert, c'est Args&&...-à- dire , puis mettre std::type_identityle 3ème paramètre comme R (T::*fun)(std::type_identity_t<Args>...).
EN
@songyuanyo oui, mais il se cassera pour l'argument valeur.
bartop
Vous pouvez déjà utiliser Forward à partir de votre démo de code . Il fera juste un mouvement "supplémentaire".
Jarod42