Fungsi Templat tidak berfungsi untuk fungsi rujukan pointer-ke-anggota

14

Akhir-akhir ini saya menulis fungsi template untuk menyelesaikan beberapa pengulangan kode. Ini terlihat seperti ini:

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

Kode ini berfungsi dengan sangat baik untuk class Ayang terlihat seperti ini:

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

    }
};

Tetapi gagal mengkompilasi untuk yang seperti ini:

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

    }
};

Mengapa demikian (maksud saya mengapa gagal menyimpulkan jenis) dan bagaimana (jika memungkinkan) saya dapat membuat kode ini berfungsi dengan referensi? Contoh langsung

Bartop
sumber
mungkin Args&&...dan std::forward?
fas
@ user3365922 mencobanya. Terasa seperti solusi, tidak berhasil
bartop
Bukankah ini dan ini membantu Anda ke arah yang benar?
Gizmo

Jawaban:

3

Masalah Anda adalah Anda memiliki pengurangan konflik Argsantara:

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

Saya sarankan untuk memiliki lebih banyak kode generik (tidak ada duplikasi antara R (T::*fun)(Args...)dan
versi const R (T::*fun)(Args...) constdan alternatif lain) dengan:

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
sumber
poin bagus tentang cv-kualifikasi fungsi anggota, saya pikir ini adalah solusi terbaik sejauh ini
bartop
8

Argstipe tidak dapat disimpulkan sebagai const&(dari fundeklarasi parameter) dan non-referensi dari argsdeklarasi. Perbaikan sederhana adalah dengan menggunakan dua paket parameter tipe templat terpisah:

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

Sebagai downside, saya bisa membayangkan pesan kesalahan yang sedikit lebih lama jika penggunaannya buruk.

LogicStuff
sumber
1
Anda mungkin inginArgs&&... args
Jarod42
5

Perhatikan bahwa tipe parameter templat Argsdisimpulkan seperti const int&pada argumen fungsi ke-3 &A::foo, dan disimpulkan seperti intpada parameter fungsi ke-4 1. Mereka tidak cocok dan menyebabkan deduksi gagal.

Anda dapat mengecualikan parameter ke-4 dari deduksi , mis

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

HIDUP

PS: std::type_identitydidukung sejak C ++ 20; tetapi cukup mudah untuk menerapkannya.

songyuanyao
sumber
1
apakah ini akan berhasil dengan penerusan yang sempurna?
Bartop
@ Bartop saya pikir begitu. Kita dapat membuat parameter ke-4 sesuai dengan gaya referensi penerusan, yaitu Args&&..., lalu memakai std::type_identityparameter ke-3 seperti R (T::*fun)(std::type_identity_t<Args>...). LIVE and LIVE
songyuanyao
@songyuanyo ya, tapi kemudian akan rusak untuk argumen nilai.
Bartop
Anda sudah dapat menggunakan maju dari Demo kode Anda . Itu hanya akan melakukan gerakan "ekstra".
Jarod42