La funzione modello non funziona per la funzione da puntatore a membro che accetta const ref


14

Ultimamente ho scritto una funzione modello per risolvere alcune ripetizioni di codice. Sembra così:

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

Questo codice funziona perfettamente per il class Aquale è simile al seguente:

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

    }
};

Ma non riesce a compilare per uno come questo:

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

    }
};

Perché è così (perché intendo perché non riesce a dedurne il tipo) e come (se è possibile farlo) posso far funzionare questo codice con i riferimenti? Esempio dal vivo


forse Args&&...e std::forward?
Fas

@ user3365922 l'ha provato. Sembra una soluzione, non funziona
bartop

Non sarebbe questo e questo aiuto nella giusta direzione?
Gizmo,

Risposte:


3

Il tuo problema è che hai detrazioni di conflitto Argstra:

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

Suggerisco di avere un codice più generico (nessuna duplicazione tra R (T::*fun)(Args...)e
versione const R (T::*fun)(Args...) conste altra alternativa) con:

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

buon punto sulla qualifica di cv della funzione membro, penso che questa sia la soluzione migliore finora
bartop

8

Argsi tipi non possono essere dedotti sia come const&(dalla fundichiarazione dei parametri) sia come non riferimento dalla argsdichiarazione. Una soluzione semplice consiste nell'utilizzare due pacchetti di parametri del tipo di modello separati:

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

Come inconveniente, posso immaginare messaggi di errore leggermente più lunghi in caso di cattivo utilizzo.


1
Probabilmente vuoiArgs&&... args
Jarod42,

5

Si noti che il Argstipo di parametro del modello viene dedotto come const int&nel terzo argomento della funzione &A::fooe come intnel quarto parametro della funzione 1. Non corrispondono e causano errori di detrazione.

È possibile escludere il quarto parametro dalla detrazione , ad es

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) {
//              ^^^^^^^^^^^^^^^^^^^^^^^^^^                

VIVERE

PS: std::type_identityè supportato da C ++ 20; ma è abbastanza facile implementarne uno.


1
funzionerà con l'inoltro perfetto in qualche modo?
bartop

@bartop Penso di sì. Possiamo rendere il 4o parametro conforme allo stile di riferimento di inoltro, cioè Args&&..., quindi inserire std::type_identityil 3o parametro come R (T::*fun)(std::type_identity_t<Args>...). LIVE e LIVE
songyuanyao il

@songyuanyo sì, ma poi si romperà per argomento di valore.
bartop

Puoi già usare forward dalla demo del tuo codice . Farà solo una mossa "extra".
Jarod42,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.