La función de plantilla no funciona para la función de puntero a miembro tomando const ref

14

Últimamente escribí una función de plantilla para resolver algunas repeticiones de código. Se parece a esto:

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

Este código funciona perfectamente bien para lo class Aque se ve así:

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

    }
};

Pero no puede compilar para uno como este:

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

    }
};

¿Por qué es así (por qué quiero decir por qué no puede deducir el tipo) y cómo (si es posible) puedo hacer que este código funcione con referencias? Ejemplo en vivo

bartop
fuente
tal vez Args&&...y std::forward?
fas
@ user3365922 lo intentó. Se siente como una solución, no funciona
bartop
¿No te ayudaría esto y esto en la dirección correcta?
Gizmo

Respuestas:

3

Su problema es que tiene deducciones por conflicto Argsentre:

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

Yo sugeriría que tener un código más genérico (no hay duplicaciones entre R (T::*fun)(Args...)y
versión const R (T::*fun)(Args...) consty otra 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());
    }
}
Jarod42
fuente
buen punto sobre cv-calificación de la función miembro, creo que esta es la mejor solución hasta ahora
bartop
8

Argslos tipos no pueden deducirse tanto como const&(de la fundeclaración de parámetros) como sin referencia de la argsdeclaración. Una solución simple es usar dos paquetes de parámetros de tipo de plantilla separados:

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

Como inconveniente, puedo imaginar mensajes de error un poco más largos en caso de mal uso.

LógicaCosas
fuente
1
Probablemente quierasArgs&&... args
Jarod42
5

Tenga en cuenta que el Argstipo de parámetro de plantilla se deduce como const int&en el argumento de la 3ª función &A::foo, y se deduce como inten el 4º parámetro de la función 1. No coinciden y hacen que la deducción falle.

Puede excluir el cuarto parámetro de la deducción , p. Ej.

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

EN VIVO

PS: std::type_identityes compatible desde C ++ 20; pero es bastante fácil implementar uno.

songyuanyao
fuente
1
¿Funcionará con reenvío perfecto de alguna manera?
bartop
@bartop, creo que sí. Podemos hacer que el 4º parámetro se ajuste al estilo de referencia de reenvío, es decir Args&&..., poner std::type_identityel 3er parámetro como R (T::*fun)(std::type_identity_t<Args>...). EN VIVO y EN VIVO
songyuanyao
@songyuanyo, sí, pero luego se romperá por el argumento del valor.
bartop
Ya puede utilizar el reenvío de su código Demo . Simplemente hará un movimiento "extra".
Jarod42