Funkcja szablonu nie działa w przypadku pobierania wskaźnika od funkcji do elementu składowego

14

Ostatnio napisałem funkcję szablonu, aby rozwiązać niektóre powtórzenia kodu. To wygląda tak:

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

Ten kod działa doskonale, dla class Aktórego wygląda tak:

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

    }
};

Ale nie można skompilować dla takiego:

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

    }
};

Dlaczego tak jest (dlaczego mam na myśli, dlaczego nie można wydedukować typu) i jak (jeśli w ogóle jest to możliwe) mogę sprawić, aby ten kod działał z referencjami? Przykład na żywo

bartop
źródło
może Args&&...i std::forward?
fas
@ user3365922 wypróbował to. Czuje się jak rozwiązanie, nie działa
bartop
Czy to i to nie pomoże ci we właściwym kierunku?
Gizmo

Odpowiedzi:

3

Problem polega na tym, że masz potrącenia konfliktowe Argsmiędzy:

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

Proponuję mieć kod bardziej ogólny (bez powielania pomiędzy R (T::*fun)(Args...)i
wersji const R (T::*fun)(Args...) consti inne alternatywy) z:

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
źródło
dobra uwaga na temat kwalifikacji cv funkcji członka, myślę, że jest to jak dotąd najlepsze rozwiązanie
bartop
8

Argstypów nie można wywnioskować zarówno jako const&(z fundeklaracji parametrów), jak i bez odniesienia z argsdeklaracji. Prostą poprawką jest użycie dwóch oddzielnych pakietów parametrów typu szablonu:

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

Jako wadę mogę sobie wyobrazić nieco dłuższe komunikaty o błędach w przypadku złego użytkowania.

LogicStuff
źródło
1
Prawdopodobnie chceszArgs&&... args
Jarod42
5

Zauważ, że Argstyp parametru szablonu jest wywnioskowany jak const int&w argumencie 3. funkcji &A::fooi wywnioskowany jak intw parametrze 4. funkcji 1. Nie pasują i powodują, że dedukcja nie udaje się.

Możesz wykluczyć czwarty parametr z odliczenia , np

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

RELACJA NA ŻYWO

PS: std::type_identityjest obsługiwany od C ++ 20; ale bardzo łatwo go wdrożyć.

songyuanyao
źródło
1
czy to jakoś zadziała z doskonałym przekazywaniem?
bartop
@bartop Myślę, że tak. Możemy sprawić, by czwarty parametr był zgodny ze stylem referencyjnym przekazywania, tj. Args&&...Następnie ustawić std::type_identitytrzeci parametr jak R (T::*fun)(std::type_identity_t<Args>...). NA ŻYWO i NA ŻYWO
songyuanyao
@ songyuanyo tak, ale wtedy pęknie argument wartości.
bartop
Możesz już użyć przekazywania z kodu Demo . Po prostu wykona „dodatkowy” ruch.
Jarod42