A função de modelo não funciona para a função ponteiro para membro que faz const ref

14

Ultimamente, escrevi uma função de modelo para resolver algumas repetições de código. Se parece com isso:

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 perfeitamente bem para o class Aqual se parece com isso:

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

    }
};

Mas falha ao compilar para um como este:

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

    }
};

Por que é assim (por que quero dizer por que não deduz o tipo) e como (se é possível) posso fazer esse código funcionar com referências? Exemplo ao vivo

bartop
fonte
talvez Args&&...e std::forward?
FAS
@ user3365922 tentei. Sente como solução, não trabalho
bartop
Não seria este e este ajudá-lo na direção certa?
Gizmo

Respostas:

3

Seu problema é que você tem deduções de conflito Argsentre:

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

Sugiro ter um código mais genérico (sem duplicações entre R (T::*fun)(Args...)e
versão const R (T::*fun)(Args...) conste outra alternativa) com:

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
fonte
bom ponto sobre cv-qualificação da função de membro, penso que esta é a melhor solução até agora
bartop
8

Argstipos não podem ser deduzidos como const&(da fundeclaração de parâmetro) e não referência da argsdeclaração. Uma correção simples é usar dois pacotes de parâmetros de tipo de modelo 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 desvantagem, posso imaginar mensagens de erro um pouco mais longas em caso de mau uso.

LogicStuff
fonte
11
Você provavelmente querArgs&&... args
Jarod42 23/10/19
5

Observe que o Argstipo de parâmetro do modelo é deduzido como const int&no argumento da 3ª função &A::fooe deduzido como intno parâmetro da 4ª função 1. Eles não coincidem e causam falhas na dedução.

Você pode excluir o quarto parâmetro da dedução , por exemplo

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

VIVER

PS: std::type_identityé suportado desde C ++ 20; mas é muito fácil implementar um.

songyuanyao
fonte
11
isso vai funcionar com encaminhamento perfeito de alguma forma?
bartop
@ Bartop Eu acho que sim. Podemos fazer com que o quarto parâmetro esteja em conformidade com o estilo de referência de encaminhamento, ou seja Args&&..., coloque std::type_identityo terceiro parâmetro como R (T::*fun)(std::type_identity_t<Args>...). LIVE and LIVE
songyuanyao 23/10/19
@songyuanyo sim, mas depois ele será interrompido pelo argumento do valor.
bartop
Você já pode usar o forward do seu código Demo . Ele fará apenas um movimento "extra".
Jarod42