Menjalankan fungsi di dalam templat fungsi hanya untuk tipe-tipe yang memiliki fungsi yang ditentukan

13

Saya memiliki templat fungsi yang membutuhkan banyak jenis input. Dari tipe-tipe itu hanya satu yang memiliki getInt()fungsi. Karenanya saya ingin kode untuk menjalankan fungsi hanya untuk tipe itu. Mohon saran solusinya. Terima kasih

#include <type_traits>
#include <typeinfo>

class X {
    public:
    int getInt(){
        return 9;
    }
};

class Y{

};

template<typename T>
void f(T& v){
    // error: 'class Y' has no member named 'getInt'
    // also tried std::is_same<T, X>::value 
    if(typeid(T).name() == typeid(X).name()){
        int i = v.getInt();// I want this to be called for X only
    }
}

int main(){
    Y y;
    f(y);
}
Sumit
sumber
Tidak terkait dengan masalah Anda, tetapi type_infostruktur memiliki operator perbandingan kesetaraan , jadi typeid(T) == typeid(X)harus bekerja juga.
Beberapa programmer Bung
5
Gunakan: if constexprdengan kondisi is_same_v<T,X>.
rafix07
Solusi untuk ini secara resmi akan menjadi lebih elegan akhir tahun ini dengan Concepts. Tidak super membantu saat ini, saya tahu.
sweenish
Ada banyak cara untuk menyelesaikan masalah Anda. Pasangan yang disebutkan di atas. Anda juga dapat menggunakan ciri - ciri varian yang berbeda untuk melihat apakah suatu tipe memiliki anggota yang dapat dipanggil getInt. Pasti ada beberapa pertanyaan di sini di stackoverflow.com sendiri tentang cara melihat apakah struktur atau kelas memiliki fungsi anggota tertentu, jika Anda hanya mencari sedikit.
Beberapa programmer Bung

Jawaban:

10

Jika Anda ingin dapat memanggil fungsi funtuk semua jenis yang memiliki anggota fungsi getInt, tidak hanya X, Anda dapat mendeklarasikan 2 kelebihan fungsi f:

  1. untuk tipe yang memiliki getIntfungsi anggota, termasuk kelasX

  2. untuk semua jenis lainnya, termasuk kelas Y.

Solusi C ++ 11 / C ++ 17

Mengingat hal itu, Anda dapat melakukan sesuatu seperti ini:

#include <iostream>
#include <type_traits>

template <typename, typename = void>
struct has_getInt : std::false_type {};

template <typename T>
struct has_getInt<T, std::void_t<decltype(((T*)nullptr)->getInt())>> : std::is_convertible<decltype(((T*)nullptr)->getInt()), int>
{};

class X {
public:
    int getInt(){
        return 9;
    }
};

class Y {};

template <typename T,
          typename std::enable_if<!has_getInt<T>::value, T>::type* = nullptr>
void f(T& v) {
    // only for Y
    std::cout << "Y" << std::endl;
}

template <typename T,
          typename std::enable_if<has_getInt<T>::value, T>::type* = nullptr>
void f(T& v){
    // only for X
    int i = v.getInt();
    std::cout << "X" << std::endl;
}

int main() {
    X x;
    f(x);

    Y y;
    f(y);
}

Lihat langsung .

Harap dicatat bahwa std::void_tdiperkenalkan di C ++ 17, tetapi jika Anda terbatas pada C ++ 11, maka sangat mudah untuk mengimplementasikannya void_tsendiri:

template <typename...>
using void_t = void;

Dan ini adalah versi live C ++ 11 .

Apa yang kita miliki di C ++ 20?

C ++ 20 membawa banyak hal baik dan salah satunya adalah konsep . Hal di atas yang berlaku untuk C ++ 11 / C ++ 14 / C ++ 17 dapat dikurangi secara signifikan dalam C ++ 20:

#include <iostream>
#include <concepts>

template<typename T>
concept HasGetInt = requires (T& v) { { v.getInt() } -> std::convertible_to<int>; };

class X {
public:
    int getInt(){
        return 9;
    }
};

class Y {};

template <typename T>
void f(T& v) {
    // only for Y
    std::cout << "Y" << std::endl;
}

template <HasGetInt T>
void f(T& v){
    // only for X
    int i = v.getInt();
    std::cout << "X" << std::endl;
}

int main() {
    X x;
    f(x);

    Y y;
    f(y);
}

Lihat langsung .

Alat pemecah buah keras
sumber
Sebelum C ++ 17, implementasi dari void_tpenyebab masalah ke beberapa kompiler lama (seperti yang ditunjukkan oleh tautan).
Jarod42
itu tidak sepenuhnya diperlukan untuk menulis dua kelebihan (mengganti "perlu" dengan "bisa" akan jauh lebih baik imho)
idclev 463035818
@ idclev463035818 diperbarui. Terima kasih
NutCracker
1
@SSAnne diperbarui
NutCracker
1
Definisi konsep tidak akurat. Anda menugaskan hasilnya ke int sehingga konsepnya seharusnyatemplate<typename T> concept HasGetInt = requires (T& v) { {v.getInt()} -> std::convertible_to<int>; };
Hui
8

Anda dapat menggunakan if constexprdari C ++ 17:

template<typename T>
void f(T& v){
    if constexpr(std::is_same_v<T, X>) { // Or better create trait has_getInt
        int i = v.getInt();// I want this to be called for X only
    }
    // ...
}

Sebelumnya, Anda harus menggunakan kelebihan beban dan SFINAE atau pengiriman tag.

Jarod42
sumber
if constexpradalah fitur C ++ 17.
Andrey Semashev
Namun, ini hanya akan berfungsi untuk kelasX
NutCracker
Pertanyaan sekarang diperbarui hanya C ++ 11 / C ++ 14
NutCracker
@NutCracker: Tidak bagus untuk memperbarui tag / pertanyaan dan karenanya membatalkan jawaban yang ada ... (bahkan jika peringatan tentang itu baik-baik saja).
Jarod42
saya baru saja memperbarui tag ... judul pertanyaan diperbarui oleh OP
NutCracker
7

Tetap sederhana dan kelebihan. Telah bekerja sejak setidaknya C ++ 98 ...

template<typename T>
void f(T& v)
{
    // do whatever
}

void f(X& v)
{
    int result = v.getInt();
}

Ini cukup jika hanya ada satu jenis dengan getIntfungsi. Jika ada lagi, itu tidak lagi sederhana. Ada beberapa cara untuk melakukannya, berikut ini:

struct PriorityA { };
struct PriorityB : PriorityA { };

template<typename T>
void f_impl(T& t, PriorityA)
{
    // generic version
}

// use expression SFINAE (-> decltype part)
// to enable/disable this overload
template<typename T>
auto f_impl(T& t, PriorityB) -> decltype(t.getInt(), void())
{
    t.getInt();
}

template<typename T>
void f(T& t)
{
    f_impl(t, PriorityB{ } ); // this will select PriorityB overload if it exists in overload set
                              // otherwise PriorityB gets sliced to PriorityA and calls generic version
}

Contoh langsung dengan hasil diagnostik.

jrok
sumber
1
Dalam hal ini ini akan berhasil karena hanya ada satu kelebihan (untuk X), tetapi, jika ada lebih banyak tipe serupa dengan anggota getIntdi masa depan, ini bukan praktik yang baik. Anda mungkin ingin mencatat bahwa
NutCracker
@NutCracker Melakukannya.
jrok