Templated memeriksa keberadaan fungsi anggota kelas?

499

Apakah mungkin untuk menulis templat yang mengubah perilaku tergantung pada apakah fungsi anggota tertentu didefinisikan pada kelas?

Berikut adalah contoh sederhana dari apa yang ingin saya tulis:

template<class T>
std::string optionalToString(T* obj)
{
    if (FUNCTION_EXISTS(T->toString))
        return obj->toString();
    else
        return "toString not defined";
}

Jadi, jika class Ttelah toString()didefinisikan, maka ia menggunakannya; kalau tidak, tidak. Bagian ajaib yang saya tidak tahu bagaimana melakukannya adalah bagian "FUNCTION_EXISTS".

andy
sumber
6
Tentu saja tidak perlu dikatakan bahwa jawaban templat di bawah ini hanya berfungsi dengan informasi waktu kompilasi, yaitu T harus memiliki toString. Jika Anda lulus dalam subclass dari T yang tidak mendefinisikan toString, tapi T tidak tidak , Anda akan diberitahu toString tidak didefinisikan.
Alice Purcell
Kemungkinan Gandakan Bagaimana memeriksa apakah nama anggota (variabel atau fungsi) ada di kelas, dengan atau tanpa jenis yang ditentukan? , karena mencakup masalah yang lebih luas dengan C ++ 03 hingga C ++ 1y.
iammilind

Jawaban:

319

Ya, dengan SFINAE Anda dapat memeriksa apakah kelas yang diberikan menyediakan metode tertentu. Ini kode kerjanya:

#include <iostream>

struct Hello
{
    int helloworld() { return 0; }
};

struct Generic {};    

// SFINAE test
template <typename T>
class has_helloworld
{
    typedef char one;
    struct two { char x[2]; };

    template <typename C> static one test( typeof(&C::helloworld) ) ;
    template <typename C> static two test(...);    

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
    std::cout << has_helloworld<Hello>::value << std::endl;
    std::cout << has_helloworld<Generic>::value << std::endl;
    return 0;
}

Saya baru saja mengujinya dengan Linux dan gcc 4.1 / 4.3. Saya tidak tahu apakah ini portabel untuk platform lain yang menjalankan kompiler berbeda.

Nicola Bonelli
sumber
18
Meskipun, saya menggunakan berikut ini untuk 'satu' dan 'dua': typedef char Small; class Big {char dummy [2];} untuk memastikan tidak ada ambiguitas tentang ukuran variabel dependen platform.
user23167
6
Saya ragu itu ada di bumi sebuah platform dengan sizeof (char) == sizeof (panjang)
Nicola Bonelli
17
Saya tidak sepenuhnya yakin, tapi saya rasa ini tidak portabel. typeof adalah ekstensi GCC, ini tidak akan bekerja pada kompiler lain.
Leon Timmermans
56
typeof tidak diperlukan - char [sizeof (& C :: helloworld)] juga berfungsi. Dan untuk menghindari sizeof (panjang) == sizeof (char), gunakan struct {char [2]} ;. Itu harus memiliki ukuran> = 2
MSalters
57
Sepele, tetapi butuh waktu beberapa saat untuk mencari tahu: ganti typeofdengan decltypesaat menggunakan C ++ 0x , misalnya, melalui -std = c ++ 0x.
jam
265

Pertanyaan ini sudah tua, tetapi dengan C ++ 11 kami mendapatkan cara baru untuk memeriksa keberadaan fungsi (atau keberadaan anggota non-tipe, benar-benar), mengandalkan SFINAE lagi:

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
    -> decltype(os << obj, void())
{
  os << obj;
}

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
    -> decltype(obj.stream(os), void())
{
  obj.stream(os);
}

template<class T>
auto serialize(std::ostream& os, T const& obj)
    -> decltype(serialize_imp(os, obj, 0), void())
{
  serialize_imp(os, obj, 0);
}

Sekarang ke beberapa penjelasan. Hal pertama, saya menggunakan ekspresi SFINAE untuk mengecualikan serialize(_imp)fungsi dari resolusi overload, jika ekspresi pertama di dalamdecltype tidak valid (alias, fungsi tidak ada).

Yang void()digunakan untuk membuat jenis kembalinya semua fungsi-fungsivoid .

The 0argumen digunakan untuk lebih memilih os << objberlebihan jika keduanya tersedia (literal 0adalah tipe intdan dengan demikian overload pertama adalah pertandingan yang lebih baik).


Sekarang, Anda mungkin ingin suatu sifat memeriksa apakah suatu fungsi ada. Untungnya, mudah untuk menulisnya. Namun, perlu diketahui bahwa Anda harus menulis sifat sendiri untuk setiap nama fungsi berbeda yang Anda inginkan.

#include <type_traits>

template<class>
struct sfinae_true : std::true_type{};

namespace detail{
  template<class T, class A0>
  static auto test_stream(int)
      -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
  template<class, class A0>
  static auto test_stream(long) -> std::false_type;
} // detail::

template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};

Contoh langsung.

Dan ke penjelasan. Pertama, sfinae_trueadalah tipe pembantu, dan pada dasarnya sama dengan menulis decltype(void(std::declval<T>().stream(a0)), std::true_type{}). Keuntungannya adalah lebih pendek.
Selanjutnya, struct has_stream : decltype(...)warisan dari salah satu std::true_typeatau std::false_typepada akhirnya, tergantung pada apakah decltypecheck-in test_streamgagal atau tidak.
Terakhir, std::declvalmemberi Anda "nilai" dari jenis apa pun yang Anda lulus, tanpa Anda perlu tahu bagaimana Anda bisa membangunnya. Perhatikan bahwa ini hanya mungkin di dalam konteks yang tidak dievaluasi, seperti decltype, sizeofdan lainnya.


Catatan yang decltypebelum tentu diperlukan, karena sizeof(dan semua konteks yang tidak dievaluasi) mendapatkan peningkatan itu. Hanya saja decltypesudah memberikan jenis dan dengan demikian hanya bersih. Ini adalah sizeofversi dari salah satu kelebihan:

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

The intdan longparameter masih ada untuk alasan yang sama. Array pointer digunakan untuk menyediakan konteks di mana sizeofdapat digunakan.

Xeo
sumber
4
Keuntungan dari decltypeover sizeofadalah juga sementara tidak diperkenalkan oleh aturan yang dibuat khusus untuk pemanggilan fungsi (jadi Anda tidak harus memiliki hak akses ke destructor dari tipe return dan tidak akan menyebabkan instantiasi tersirat jika tipe return adalah contoh template kelas).
Johannes Schaub - litb
5
Microsoft belum mengimplementasikan Expression SFINAE di kompiler C ++-nya. Bayangkan saja saya dapat membantu menghemat waktu orang, karena saya bingung mengapa ini tidak berhasil bagi saya. Solusi yang bagus, tidak sabar untuk menggunakannya di Visual Studio!
Jonathan
3
Tautan contoh pertama Anda rusak
NathanOliver
1
Harus dikatakan, itu static_assert(has_stream<X, char>() == true, "fail X");akan dikompilasi dan tidak menegaskan karena char dapat dikonversi ke int, jadi jika perilaku itu tidak diinginkan dan ingin semua jenis argumen cocok, saya tidak tahu bagaimana itu bisa dicapai?
Gabriel
4
Jika Anda sama bingungnya dengan saya pada dua argumen untuk decltype: decltype benar-benar hanya membutuhkan satu; koma adalah operator di sini. Lihat stackoverflow.com/questions/16044514/…
André
159

C ++ memungkinkan SFINAE untuk digunakan untuk ini (perhatikan bahwa dengan fitur C ++ 11 ini lebih sederhana karena mendukung SFINAE yang diperluas pada ekspresi yang hampir sewenang-wenang - di bawah ini dibuat untuk bekerja dengan kompiler C ++ 03 yang umum):

#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
    struct name {                                                       \
        typedef char yes[1];                                            \
        typedef char no [2];                                            \
        template <typename U, U> struct type_check;                     \
        template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
        template <typename   > static no  &chk(...);                    \
        static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
    }

template dan makro di atas mencoba untuk instantiate templat, memberinya jenis pointer fungsi anggota, dan pointer fungsi anggota yang sebenarnya. Jika jenisnya tidak sesuai, SFINAE menyebabkan templat diabaikan. Penggunaan seperti ini:

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> void
doSomething() {
   if(has_to_string<T, std::string(T::*)()>::value) {
      ...
   } else {
      ...
   }
}

Tetapi perhatikan bahwa Anda tidak bisa hanya memanggil toStringfungsi itu di cabang if. karena kompiler akan memeriksa validitas di kedua cabang, itu akan gagal untuk kasus fungsi tidak ada. Salah satu caranya adalah dengan menggunakan SFINAE sekali lagi (enable_if dapat diperoleh dari boost juga):

template<bool C, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> { };

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> 
typename enable_if<has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T has toString ... */
   return t->toString();
}

template<typename T> 
typename enable_if<!has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T doesnt have toString ... */
   return "T::toString() does not exist.";
}

Bersenang-senang menggunakannya. Keuntungannya adalah ia juga berfungsi untuk fungsi anggota yang kelebihan beban, dan juga untuk fungsi anggota konst (ingat gunakan std::string(T::*)() constsebagai tipe fungsi penunjuk anggota lalu!).

Johannes Schaub - litb
sumber
7
Saya suka bagaimana type_checkdigunakan untuk memastikan bahwa tanda tangan setuju persis. Apakah ada cara untuk membuatnya sehingga akan cocok dengan metode apa pun yang dapat dipanggil dengan cara yang disebut metode dengan tanda tangan Sign? (Misalnya, jika Sign= std::string(T::*)(), memungkinkan std::string T::toString(int default = 42, ...)untuk mencocokkan.)
j_random_hacker
5
Saya hanya mencari tahu tentang hal ini yang tidak langsung jelas bagi saya, jadi jika itu membantu orang lain: chk tidak dan tidak perlu didefinisikan! Ukuran operator menentukan ukuran output dari chk tanpa chk perlu dipanggil.
SCFrench
3
@ deek0146: Ya, Tharus bukan tipe primitif, karena deklarasi pointer-to-method-of-T tidak tunduk pada SFINAE dan akan kesalahan keluar untuk T. non-kelas T. IMO solusi termudah adalah menggabungkan dengan is_classmemeriksa dari dorongan.
Jan Hudec
2
Bagaimana saya bisa membuat ini berfungsi jika toStringfungsi saya templated?
Frank
4
Apakah ini (atau apa pun yang setara) dalam Peningkatan?
Dan Nissenbaum
89

C ++ 20 - requiresekspresi

Dengan C ++ 20 datang konsep dan berbagai macam alat seperti requiresekspresi yang merupakan cara bawaan untuk memeriksa keberadaan fungsi. Dengan mereka Anda dapat menulis ulang optionalToStringfungsi Anda sebagai berikut:

template<class T>
std::string optionalToString(T* obj)
{
    constexpr bool has_toString = requires(const T& t) {
        t.toString();
    };

    if constexpr (has_toString)
        return obj->toString();
    else
        return "toString not defined";
}

Pra-C ++ 20 - Toolkit deteksi

N4502 mengusulkan toolkit deteksi untuk dimasukkan ke dalam pustaka standar C ++ 17 yang akhirnya membuatnya menjadi pustaka dasar TS v2. Kemungkinan besar tidak akan pernah masuk ke dalam standar karena sudah dimasukkan oleh requiresekspresi sejak itu, tetapi masih memecahkan masalah dengan cara yang agak elegan. Toolkit ini memperkenalkan beberapa metafungsi, termasuk std::is_detectedyang dapat digunakan untuk dengan mudah menulis metafungsi deteksi tipe atau fungsi di atasnya. Inilah cara Anda dapat menggunakannya:

template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );

template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;

Perhatikan bahwa contoh di atas tidak diuji. Toolkit deteksi belum tersedia di perpustakaan standar tetapi proposal berisi implementasi penuh yang dapat Anda salin dengan mudah jika Anda benar-benar membutuhkannya. Bermain bagus dengan fitur C ++ 17 if constexpr:

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr (has_toString<T>)
        return obj->toString();
    else
        return "toString not defined";
}

C ++ 14 - Tingkatkan .Hana

Boost.Hana tampaknya membangun contoh spesifik ini dan memberikan solusi untuk C ++ 14 dalam dokumentasinya, jadi saya akan mengutipnya secara langsung:

[...] Hana menyediakan is_validfungsi yang dapat dikombinasikan dengan lambda generik C ++ 14 untuk mendapatkan implementasi yang jauh lebih bersih dari hal yang sama:

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

Ini meninggalkan kita dengan objek fungsi has_toStringyang mengembalikan apakah ekspresi yang diberikan valid pada argumen yang kita berikan. Hasilnya dikembalikan sebagai IntegralConstant, jadi ketekunan tidak menjadi masalah di sini karena hasil fungsi direpresentasikan sebagai tipe. Sekarang, selain menjadi kurang bertele-tele (itu satu kalimat!), Maksudnya jauh lebih jelas. Manfaat lain adalah fakta yang has_toStringdapat diteruskan ke algoritme tingkat tinggi dan juga dapat didefinisikan pada lingkup fungsi, sehingga tidak perlu mencemari ruang lingkup namespace dengan detail implementasi.

Tingkatkan.TTI

Toolkit lain yang agak idiomatis untuk melakukan pemeriksaan semacam itu - meskipun kurang elegan - adalah Boost.TTI , diperkenalkan dalam Boost 1.54.0. Sebagai contoh, Anda harus menggunakan makro BOOST_TTI_HAS_MEMBER_FUNCTION. Inilah cara Anda dapat menggunakannya:

#include <boost/tti/has_member_function.hpp>

// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)

// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;

Kemudian, Anda bisa menggunakan booluntuk membuat cek SFINAE.

Penjelasan

Makro BOOST_TTI_HAS_MEMBER_FUNCTIONmenghasilkan metafungsi has_member_function_toStringyang mengambil tipe yang diperiksa sebagai parameter templat pertama. Parameter templat kedua sesuai dengan jenis pengembalian fungsi anggota, dan parameter berikut ini sesuai dengan jenis parameter fungsi. Anggota valueberisi truejika kelas Tmemiliki fungsi anggota std::string toString().

Atau, has_member_function_toStringdapat mengambil pointer fungsi anggota sebagai parameter template. Oleh karena itu, dimungkinkan untuk mengganti has_member_function_toString<T, std::string>::valuedengan has_member_function_toString<std::string T::* ()>::value.

Morwenn
sumber
1
lebih ringkas dari 03
ZFY
@ZFY Saya berpikir bahwa Boost.TTI juga bekerja dengan C ++ 03, tapi ini adalah solusi yang paling tidak elegan.
Morwenn
Apakah solusi C ++ 20 benar-benar valid? Saya ingin - tetapi ditolak oleh g ++ dan msvc - hanya diterima oleh dentang.
Bernd Baumanns
di cppreference Anda dapat membaca: Jika membutuhkan-ekspresi berisi jenis atau ekspresi yang tidak valid dalam persyaratannya, dan itu tidak muncul dalam deklarasi entitas templated, maka program ini salah bentuk.
Bernd Baumanns
@BerndBaumanns Benarkah? Saya berhasil menggunakan trunk GCC: godbolt.org/z/CBwZdE Mungkin Anda benar, saya hanya memeriksa apakah itu berfungsi tetapi tidak memeriksa apakah itu legal menurut kata-kata standar.
Morwenn
56

Meskipun pertanyaan ini berumur dua tahun, saya akan berani menambahkan jawaban saya. Semoga ini akan menjelaskan solusi yang sebelumnya, sangat bagus, tak terbantahkan. Saya mengambil jawaban yang sangat membantu dari Nicola Bonelli dan Johannes Schaub dan menggabungkannya menjadi solusi yang, IMHO, lebih mudah dibaca, jelas, dan tidak memerlukan typeofekstensi:

template <class Type>
class TypeHasToString
{
    // This type won't compile if the second template parameter isn't of type T,
    // so I can put a function pointer type in the first parameter and the function
    // itself in the second thus checking that the function has a specific signature.
    template <typename T, T> struct TypeCheck;

    typedef char Yes;
    typedef long No;

    // A helper struct to hold the declaration of the function pointer.
    // Change it if the function signature changes.
    template <typename T> struct ToString
    {
        typedef void (T::*fptr)();
    };

    template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
    template <typename T> static No  HasToString(...);

public:
    static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};

Saya memeriksanya dengan gcc 4.1.2. Penghargaan terutama diberikan kepada Nicola Bonelli dan Johannes Schaub, jadi beri mereka suara jika jawaban saya membantu Anda :)

FireAphis
sumber
1
Hanya ingin tahu, apakah ini melakukan sesuatu yang tidak dilakukan oleh solusi Konrad Rudolph di bawah ini?
Alastair Irvine
3
@AlastairIrvine, solusi ini menyembunyikan semua logika di dalamnya, Konrad's menempatkan beberapa beban pada pengguna. Meskipun pendek dan jauh lebih mudah dibaca, solusi Konrad membutuhkan spesialisasi templat terpisah untuk setiap kelas yang dimiliki toString. Jika Anda menulis pustaka generik, yang ingin bekerja dengan kelas apa pun di luar sana (pikirkan sesuatu seperti dorongan), maka mengharuskan pengguna untuk menentukan spesialisasi tambahan dari beberapa templat yang tidak jelas mungkin tidak dapat diterima. Terkadang lebih baik menulis kode yang sangat rumit agar antarmuka publik sesederhana mungkin.
FireAphis
30

Solusi sederhana untuk C ++ 11:

template<class T>
auto optionalToString(T* obj)
 -> decltype(  obj->toString()  )
{
    return     obj->toString();
}
auto optionalToString(...) -> string
{
    return "toString not defined";
}

Pembaruan, 3 tahun kemudian: (dan ini belum diuji). Untuk menguji keberadaannya, saya pikir ini akan berhasil:

template<class T>
constexpr auto test_has_toString_method(T* obj)
 -> decltype(  obj->toString() , std::true_type{} )
{
    return     obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
    return "toString not defined";
}
Aaron McDaid
sumber
4
Ini sederhana dan elegan, tetapi sebenarnya tidak menjawab pertanyaan OP: Anda tidak mengaktifkan pemanggil untuk memeriksa keberadaan suatu fungsi, Anda selalu menyediakannya . Tapi bagus kok.
Adrian W
@AdrianW, poin bagus. Saya telah memperbarui jawaban saya. Saya belum mengujinya
Aaron McDaid
Jika itu membantu orang lain, saya tidak bisa membuat ini bekerja template<typename>sebelum variadic overload: itu tidak dipertimbangkan untuk resolusi.
Laboratorio Cobotica
Sekali lagi, ini tidak valid C ++ 11.
Peter
29

Inilah jenis sifat yang ada untuk. Sayangnya, mereka harus didefinisikan secara manual. Dalam kasus Anda, bayangkan berikut ini:

template <typename T>
struct response_trait {
    static bool const has_tostring = false;
};

template <>
struct response_trait<your_type_with_tostring> {
    static bool const has_tostring = true;
}
Konrad Rudolph
sumber
5
Anda harus lebih memilih enum untuk ciri daripada konstanta statis: "Anggota konstanta statis adalah nilai, yang memaksa kompiler untuk instantiate dan mengalokasikan definisi untuk anggota statis. Akibatnya, perhitungan tidak lagi terbatas pada waktu kompilasi murni" "efek."
Özgür
5
"Nilai enumerasi bukan lvalues ​​(yaitu, mereka tidak memiliki alamat). Jadi, ketika Anda memberikannya" dengan referensi, "tidak ada memori statis yang digunakan. Ini hampir sama seperti jika Anda melewati nilai yang dihitung sebagai literal Pertimbangan ini memotivasi kami untuk menggunakan nilai enumerasi "C ++ Templates: The Complete Guide
Özgür
22
Comptrol: tidak, kutipan yang dikutip tidak berlaku di sini karena konstanta statis tipe integer adalah kasus khusus! Mereka berperilaku persis seperti enum di sini dan merupakan cara yang disukai. Retas enum lama hanya diperlukan pada kompiler yang tidak mengikuti standar C ++.
Konrad Rudolph
3
@Roger Pate: Tidak cukup. "Digunakan dalam program" di sini rupanya identik dengan "direferensikan". Pembacaan yang berlaku dari bagian ini, dan yang diimplementasikan oleh semua kompiler C ++ modern, adalah bahwa Anda dapat mengambil nilai konstanta statis tanpa perlu mendeklarasikannya (kalimat sebelumnya mengatakan ini: "... anggota dapat muncul dalam ekspresi konstanta konstan integral ... "). Anda hanya perlu mendefinisikannya jika Anda mengambil alamatnya (secara eksplisit melalui &T::xatau secara implisit dengan mengikatnya ke referensi).
Konrad Rudolph
25

Nah, pertanyaan ini sudah memiliki daftar jawaban yang panjang, tetapi saya ingin menekankan komentar dari Morwenn: ada proposal untuk C ++ 17 yang membuatnya sangat sederhana. Lihat N4502 untuk detailnya, tetapi sebagai contoh lengkap pertimbangkan hal berikut.

Bagian ini adalah bagian konstan, taruh di header.

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

kemudian ada bagian variabel, di mana Anda menentukan apa yang Anda cari (tipe, tipe anggota, fungsi, fungsi anggota dll.). Dalam hal OP:

template <typename T>
using toString_t = decltype(std::declval<T>().toString());

template <typename T>
using has_toString = detect<T, toString_t>;

Contoh berikut, diambil dari N4502 , menunjukkan penyelidikan yang lebih rumit:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

Dibandingkan dengan implementasi lain yang dijelaskan di atas, yang ini cukup sederhana: set alat yang dikurangi ( void_tdan detect) cukup, tidak perlu makro berbulu. Selain itu, dilaporkan (lihat N4502 ) bahwa itu terukur lebih efisien (kompilasi waktu dan konsumsi memori kompiler) daripada pendekatan sebelumnya.

Ini adalah contoh nyata . Ini berfungsi baik dengan Dentang, tetapi sayangnya, versi GCC sebelum 5.1 mengikuti interpretasi yang berbeda dari standar C ++ 11 yang menyebabkan void_ttidak berfungsi seperti yang diharapkan. Yakk sudah menyediakan solusi: gunakan definisi berikut dari void_t( void_t dalam daftar parameter berfungsi tetapi tidak sebagai tipe pengembalian ):

#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
  using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif
akim
sumber
Apakah mungkin memperluasnya untuk mendeteksi fungsi yang bukan anggota?
plasmacel
Ya tentu. Perhatikan dengan cermat contoh-contohnya: pada dasarnya Anda memberikan ekspresi dan memeriksa apakah itu valid. Tidak ada yang membutuhkan ungkapan ini hanya tentang panggilan fungsi anggota.
akim
N4502 ( open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf ) adalah cara masa depan ... Saya mencari cara yang rapi untuk mendeteksi hal-hal pada jenis dan N4502 adalah caranya untuk pergi.
tlonuk
11

Ini adalah solusi C ++ 11 untuk masalah umum jika "Jika saya melakukan X, apakah akan dikompilasi?"

template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
  T,
  type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};

Ciri has_to_stringseperti itu has_to_string<T>::valueadalah truejika dan hanya jika Tmemiliki metode .toStringyang dapat dipanggil dengan 0 argumen dalam konteks ini.

Selanjutnya, saya akan menggunakan pengiriman tag:

namespace details {
  template<class T>
  std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
    return obj->toString();
  }
  template<class T>
  std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
    return "toString not defined";
  }
}
template<class T>
std::string optionalToString(T* obj) {
  return details::optionalToString_helper( obj, has_to_string<T>{} );
}

yang cenderung lebih dapat dikelola daripada ekspresi SFINAE kompleks.

Anda dapat menulis sifat-sifat ini dengan makro jika Anda menemukan diri Anda melakukannya banyak, tetapi mereka relatif sederhana (masing-masing beberapa baris) jadi mungkin tidak sepadan:

#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};

apa yang dilakukan di atas adalah membuat makro MAKE_CODE_TRAIT. Anda memberikan nama sifat yang Anda inginkan, dan beberapa kode yang dapat menguji jenisnya T. Jadi:

MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )

menciptakan kelas ciri di atas.

Selain itu, teknik di atas adalah bagian dari apa yang disebut MS "ekspresi SFINAE", dan kompiler 2013 gagal cukup keras.

Perhatikan bahwa dalam C ++ 1y sintaks berikut ini dimungkinkan:

template<class T>
std::string optionalToString(T* obj) {
  return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
    return obj.toString();
  }) *compiled_else ([&]{ 
    return "toString not defined";
  });
}

yang merupakan cabang bersyarat kompilasi inline yang menyalahgunakan banyak fitur C ++. Melakukannya mungkin tidak sepadan, karena manfaat (dari kode yang sebaris) tidak sebanding dengan biaya (di samping tidak ada yang mengerti cara kerjanya), tetapi keberadaan solusi di atas mungkin menarik.

Yakk - Adam Nevraumont
sumber
Apakah ini menangani kasus pribadi?
tower120
@ tower120 Saya harus bereksperimen: bagaimana templat berinteraksi dengan privat / publik / terproteksi agak tidak jelas bagi saya. Tidak masalah di mana Anda memohon has_to_string.
Yakk - Adam Nevraumont
tetapi Anda tahu, jika melihat dari sisi lain ... Kita dapat menjangkau anggota yang dilindungi dari kelas Berasal. Mungkin jika meletakkan semua ini di kelas INSIDE, dan mengonversi dari struct ke fungsi constexpr ...
tower120
Di sini, lihat coliru.stacked-crooked.com/a/ee94d16e7c07e093 ini saya tidak bisa membuatnya constexpr
tower120
@ tower120 C ++ 1y membuatnya bekerja: coliru.stacked-crooked.com/a/d8cdfff24a171394
Yakk - Adam Nevraumont
10

Berikut ini beberapa cuplikan penggunaan: * Nyali untuk semua ini lebih jauh ke bawah

Periksa anggota xdi kelas yang diberikan. Bisa berupa var, func, class, union, atau enum:

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;

Periksa fungsi anggota void x():

//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;

Periksa variabel anggota x:

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

Periksa kelas anggota x:

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

Periksa serikat anggota x:

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

Periksa enum anggota x:

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

Periksa fungsi anggota apa pun xtanpa tanda tangan:

CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

ATAU

CREATE_MEMBER_CHECKS(x);  //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

Detail dan inti:

/*
    - Multiple inheritance forces ambiguity of member names.
    - SFINAE is used to make aliases to member names.
    - Expression SFINAE is used in just one generic has_member that can accept
      any alias we pass it.
*/

//Variadic to force ambiguity of class members.  C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};

//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};

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

template<typename A>
struct got_type<A> : std::true_type {
    typedef A type;
};

template<typename T, T>
struct sig_check : std::true_type {};

template<typename Alias, typename AmbiguitySeed>
struct has_member {
    template<typename C> static char ((&f(decltype(&C::value))))[1];
    template<typename C> static char ((&f(...)))[2];

    //Make sure the member name is consistently spelled the same.
    static_assert(
        (sizeof(f<AmbiguitySeed>(0)) == 1)
        , "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
    );

    static bool const value = sizeof(f<Alias>(0)) == 2;
};

Makro (El Diablo!):

CREATE_MEMBER_CHECK:

//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member)                                         \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct Alias_##member;                                                      \
                                                                            \
template<typename T>                                                        \
struct Alias_##member <                                                     \
    T, std::integral_constant<bool, got_type<decltype(&T::member)>::value>  \
> { static const decltype(&T::member) value; };                             \
                                                                            \
struct AmbiguitySeed_##member { char member; };                             \
                                                                            \
template<typename T>                                                        \
struct has_member_##member {                                                \
    static const bool value                                                 \
        = has_member<                                                       \
            Alias_##member<ambiguate<T, AmbiguitySeed_##member>>            \
            , Alias_##member<AmbiguitySeed_##member>                        \
        >::value                                                            \
    ;                                                                       \
}

CREATE_MEMBER_VAR_CHECK:

//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name)                                   \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_var_##var_name : std::false_type {};                      \
                                                                            \
template<typename T>                                                        \
struct has_member_var_##var_name<                                           \
    T                                                                       \
    , std::integral_constant<                                               \
        bool                                                                \
        , !std::is_member_function_pointer<decltype(&T::var_name)>::value   \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_SIG_CHECK:

//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix)    \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_func_##templ_postfix : std::false_type {};                \
                                                                            \
template<typename T>                                                        \
struct has_member_func_##templ_postfix<                                     \
    T, std::integral_constant<                                              \
        bool                                                                \
        , sig_check<func_sig, &T::func_name>::value                         \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_CLASS_CHECK:

//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_class_##class_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_class_##class_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_class<                                    \
            typename got_type<typename T::class_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_UNION_CHECK:

//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_union_##union_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_union_##union_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_union<                                    \
            typename got_type<typename T::union_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_ENUM_CHECK:

//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name)                 \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_enum_##enum_name : std::false_type {};    \
                                                            \
template<typename T>                                        \
struct has_member_enum_##enum_name<                         \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_enum<                                     \
            typename got_type<typename T::enum_name>::type  \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_CHECK:

//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func)          \
template<typename T>                            \
struct has_member_func_##func {                 \
    static const bool value                     \
        = has_member_##func<T>::value           \
        && !has_member_var_##func<T>::value     \
        && !has_member_class_##func<T>::value   \
        && !has_member_union_##func<T>::value   \
        && !has_member_enum_##func<T>::value    \
    ;                                           \
}

CREATE_MEMBER_CHECKS:

//Create all the checks for one member.  Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member)    \
CREATE_MEMBER_CHECK(member);            \
CREATE_MEMBER_VAR_CHECK(member);        \
CREATE_MEMBER_CLASS_CHECK(member);      \
CREATE_MEMBER_UNION_CHECK(member);      \
CREATE_MEMBER_ENUM_CHECK(member);       \
CREATE_MEMBER_FUNC_CHECK(member)
Brett Rossier
sumber
1
apakah Anda tahu mengapa jika kami beralih sig_check<func_sig, &T::func_name>ke pengecekan fungsi bebas: sig_check<func_sig, &func_name>gagal dibangun dengan "pengidentifikasi yang tidak dideklarasikan" dengan menyebutkan nama fungsi yang ingin kami periksa? karena saya berharap SFINAE TIDAK membuat kesalahan, itu hanya untuk anggota, mengapa tidak untuk fungsi gratis?
v.oddou
Saya berasumsi itu ada hubungannya dengan fakta bahwa fungsi bebas bukan kelas atau struct. Teknik deduksi kehadiran anggota ini benar-benar berpusat pada mekanisme pewarisan berganda di C ++ memaksa ambiguitas antara kelas rintisan yang hanya ada untuk tujuan hosting anggota yang Anda periksa vs kelas yang sebenarnya Anda periksa untuk anggota tersebut masuk. Itu pertanyaan yang menarik, belum memikirkannya. Anda mungkin memeriksa sekitar untuk C + + 11/14 teknik anggota-cek lainnya, saya telah melihat beberapa hal pintar dalam standar baru.
Brett Rossier
Terima kasih atas jawaban Anda, saya pikir saya mungkin harus memeriksa lebih dalam bahwa intel yang Anda berikan tentang warisan, karena sampai sekarang saya tidak melihat ada korelasi antara hanya mengandalkan SFINAE untuk membuat ekspresi yang tidak benar untuk mengekspresikan akses ke anggota dalam parameter tipe templat, dan banyak pewarisan. Tapi saya benar-benar percaya bahwa dalam C ++ bahkan konsep yang jauh dapat berdarah satu sama lain. Sekarang untuk fungsi gratis, pertanyaan ini menarik: stackoverflow.com/questions/26744589 Jawaban TC tampaknya menggunakan trik untuk mendeklarasikan dummy untuk menghindari "pengidentifikasi yang tidak dideklarasikan"
v.oddou
8

Saya menulis jawaban untuk ini di utas lain yang (tidak seperti solusi di atas) juga memeriksa fungsi anggota yang diwarisi:

SFINAE untuk memeriksa fungsi anggota yang diwarisi

Berikut adalah beberapa contoh dari solusi itu:

Contoh 1:

Kami sedang memeriksa seorang anggota dengan tanda tangan berikut: T::const_iterator begin() const

template<class T> struct has_const_begin
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U> 
    static Yes test(U const * data, 
                    typename std::enable_if<std::is_same<
                             typename U::const_iterator, 
                             decltype(data->begin())
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};

Harap perhatikan bahwa ini bahkan memeriksa keteguhan metode ini, dan bekerja dengan tipe primitif juga. (Maksudkuhas_const_begin<int>::value salah dan tidak menyebabkan kesalahan waktu kompilasi.)

Contoh 2

Sekarang kami sedang mencari tanda tangan: void foo(MyClass&, unsigned)

template<class T> struct has_foo
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U>
    static Yes test(U * data, MyClass* arg1 = 0,
                    typename std::enable_if<std::is_void<
                             decltype(data->foo(*arg1, 1u))
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};

Harap perhatikan bahwa MyClass tidak harus dapat dibangun secara default atau untuk memenuhi konsep khusus apa pun. Teknik ini bekerja dengan anggota template, juga.

Saya tidak sabar menunggu pendapat tentang ini.

Kispaljr
sumber
7

Sekarang ini menyenangkan teka-teki kecil yang - pertanyaan bagus!

Inilah alternatif untuk solusi Nicola Bonelli yang tidak bergantung pada typeofoperator yang tidak standar .

Sayangnya, ini tidak bekerja pada GCC (MinGW) 3.4.5 atau Digital Mars 8.42n, tetapi ini bekerja pada semua versi MSVC (termasuk VC6) dan pada Comeau C ++.

Blok komentar yang lebih panjang memiliki detail tentang cara kerjanya (atau seharusnya berfungsi). Seperti yang dikatakan, saya tidak yakin perilaku mana yang sesuai standar - saya akan menyambut komentar tentang itu.


pembaruan - 7 Nov 2008:

Sepertinya kode ini benar secara sintaksis, perilaku yang ditunjukkan oleh MSVC dan Comeau C ++ tidak mengikuti standar (terima kasih kepada Leon Timmermans dan litb karena telah mengarahkan saya ke arah yang benar). Standar C ++ 03 mengatakan yang berikut:

14.6.2 Nama tergantung [temp.dep]

Paragraf 3

Dalam definisi templat kelas atau anggota templat kelas, jika kelas dasar templat kelas bergantung pada templat-parameter, ruang lingkup kelas dasar tidak diperiksa selama pencarian nama yang tidak berkualitas baik pada titik definisi kelas. templat atau anggota atau selama instantiasi templat kelas atau anggota.

Jadi, sepertinya ketika MSVC atau Comeau mempertimbangkan toString()fungsi anggota Tmelakukan pencarian nama di situs panggilan didoToString() ketika template dibuat, itu tidak benar (meskipun sebenarnya perilaku yang saya cari dalam kasus ini).

Perilaku GCC dan Digital Mars terlihat benar - dalam kedua kasus, toString()fungsi non-anggota terikat pada panggilan.

Tikus - Saya pikir saya mungkin telah menemukan solusi yang cerdas, alih-alih saya menemukan beberapa bug penyusun ...


#include <iostream>
#include <string>

struct Hello
{
    std::string toString() {
        return "Hello";
    }
};

struct Generic {};


// the following namespace keeps the toString() method out of
//  most everything - except the other stuff in this
//  compilation unit

namespace {
    std::string toString()
    {
        return "toString not defined";
    }

    template <typename T>
    class optionalToStringImpl : public T
    {
    public:
        std::string doToString() {

            // in theory, the name lookup for this call to 
            //  toString() should find the toString() in 
            //  the base class T if one exists, but if one 
            //  doesn't exist in the base class, it'll 
            //  find the free toString() function in 
            //  the private namespace.
            //
            // This theory works for MSVC (all versions
            //  from VC6 to VC9) and Comeau C++, but
            //  does not work with MinGW 3.4.5 or 
            //  Digital Mars 8.42n
            //
            // I'm honestly not sure what the standard says 
            //  is the correct behavior here - it's sort 
            //  of like ADL (Argument Dependent Lookup - 
            //  also known as Koenig Lookup) but without
            //  arguments (except the implied "this" pointer)

            return toString();
        }
    };
}

template <typename T>
std::string optionalToString(T & obj)
{
    // ugly, hacky cast...
    optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);

    return temp->doToString();
}



int
main(int argc, char *argv[])
{
    Hello helloObj;
    Generic genericObj;

    std::cout << optionalToString( helloObj) << std::endl;
    std::cout << optionalToString( genericObj) << std::endl;
    return 0;
}
Michael Burr
sumber
1
Tidak, ini tidak sesuai standar, meskipun saya pikir ini akan berfungsi di GCC jika Anda mengaktifkan opsi -fpermissive.
Leon Timmermans
Saya tahu komentar tidak memberikan banyak ruang, tetapi bisakah Anda menunjukkan informasi mengapa itu tidak sesuai standar? (Saya tidak berdebat - Saya ingin tahu)
Michael Burr
Mike B: standar mengatakan dalam 3.10 hal 15: "Jika suatu program mencoba untuk mengakses nilai yang disimpan dari suatu objek melalui nilai lebih dari satu dari jenis berikut perilaku tidak terdefinisi" dan daftar itu memang tidak termasuk kasus Anda melakukan.
Johannes Schaub - litb
4
saya tidak yakin mengapa itu tidak menambahkan komentar saya: panggilan toString Anda tidak memenuhi syarat. jadi itu akan selalu memanggil fungsi bebas dan tidak pernah yang ada di basis, karena baseclass tergantung pada parameter tipe templat.
Johannes Schaub - litb
@ litb: Terima kasih untuk petunjuknya. Saya tidak berpikir 3,10 berlaku di sini. Panggilan ke toString () di dalam doToString () tidak "mengakses nilai yang disimpan dari suatu objek melalui nilai". Tetapi komentar kedua Anda benar. Saya akan memperbarui jawabannya.
Michael Burr
6

Solusi C ++ standar yang disajikan di sini oleh litb tidak akan berfungsi seperti yang diharapkan jika metode tersebut akan didefinisikan dalam kelas dasar.

Untuk solusi yang menangani situasi ini lihat:

Dalam bahasa Rusia: http://www.rsdn.ru/forum/message/2759773.1.aspx

Terjemahan bahasa Inggris oleh Roman.Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

Ini sangat pintar. Namun satu masalah dengan solutiion ini adalah yang memberikan kesalahan kompiler jika tipe yang diuji adalah yang tidak dapat digunakan sebagai kelas dasar (misalnya tipe primitif)

Dalam Visual Studio, saya perhatikan bahwa jika bekerja dengan metode yang tidak memiliki argumen, sepasang tambahan redundan () perlu dimasukkan di sekitar argumen untuk menyimpulkan () dalam ukuran ekspresi.


sumber
Hmm, setelah mengembangkan versi saya sendiri menggunakan ide-ide posting itu, saya menemukan ide memiliki beberapa kelemahan lain jadi saya menghapus kode dari jawaban saya lagi. Salah satunya adalah bahwa semua fungsi harus publik dalam tipe target. Jadi, Anda tidak dapat memeriksa fungsi "f" dalam hal ini: struct g { void f(); private: void f(int); };karena salah satu fungsi bersifat pribadi (ini karena kodnya using g::f;, yang membuatnya gagal jika ada fyang tidak dapat diakses).
Johannes Schaub - litb
6

MSVC memiliki kata kunci __if_exists dan __if_not_exists ( Doc ). Bersama-sama dengan pendekatan typeof-SFINAE dari Nicola saya bisa membuat pemeriksaan untuk GCC dan MSVC seperti yang dicari OP.

Pembaruan: Sumber dapat ditemukan Di Sini

bangsawan
sumber
6

Contoh menggunakan SFINAE dan spesialisasi templat sebagian, dengan menulis Has_foocek konsep:

#include <type_traits>
struct A{};

struct B{ int foo(int a, int b);};

struct C{void foo(int a, int b);};

struct D{int foo();};

struct E: public B{};

// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;

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

template<typename T> 
struct Has_foo<T, void_t<
    std::enable_if_t<
        std::is_same<
            int, 
            decltype(std::declval<T>().foo((int)0, (int)0))
        >::value
    >
>>: std::true_type{};


static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");
Paul Belanger
sumber
5

Saya memodifikasi solusi yang disediakan di https://stackoverflow.com/a/264088/2712152 untuk membuatnya sedikit lebih umum. Juga karena tidak menggunakan salah satu fitur C ++ 11 yang baru, kita dapat menggunakannya dengan kompiler lama dan juga harus bekerja dengan msvc. Tetapi kompiler harus memungkinkan C99 untuk menggunakan ini karena ia menggunakan makro variadic.

Makro berikut dapat digunakan untuk memeriksa apakah kelas tertentu memiliki typedef tertentu atau tidak.

/** 
 * @class      : HAS_TYPEDEF
 * @brief      : This macro will be used to check if a class has a particular
 * typedef or not.
 * @param typedef_name : Name of Typedef
 * @param name  : Name of struct which is going to be run the test for
 * the given particular typedef specified in typedef_name
 */
#define HAS_TYPEDEF(typedef_name, name)                           \
   template <typename T>                                          \
   struct name {                                                  \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U>                                       \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<typename _1::typedef_name>*);    \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Makro berikut dapat digunakan untuk memeriksa apakah kelas tertentu memiliki fungsi anggota tertentu atau tidak dengan sejumlah argumen yang diberikan.

/** 
 * @class      : HAS_MEM_FUNC
 * @brief      : This macro will be used to check if a class has a particular
 * member function implemented in the public section or not. 
 * @param func : Name of Member Function
 * @param name : Name of struct which is going to be run the test for
 * the given particular member function name specified in func
 * @param return_type: Return type of the member function
 * @param ellipsis(...) : Since this is macro should provide test case for every
 * possible member function we use variadic macros to cover all possibilities
 */
#define HAS_MEM_FUNC(func, name, return_type, ...)                \
   template <typename T>                                          \
   struct name {                                                  \
      typedef return_type (T::*Sign)(__VA_ARGS__);                \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U, U>                                    \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<Sign, &_1::func>*);              \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Kita dapat menggunakan 2 makro di atas untuk melakukan pemeriksaan has_typedef dan has_mem_func sebagai:

class A {
public:
  typedef int check;
  void check_function() {}
};

class B {
public:
  void hello(int a, double b) {}
  void hello() {}
};

HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);

int main() {
  std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
  std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
  std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
  std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
  std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
  std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
  std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
  std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}
Shubham
sumber
Anda dapat meningkatkan ini untuk mendukung fungsi anggota dengan argumen templat. Ubah template <typename T> menjadi templat <typename T, typename ... Args>, maka Anda dapat menggunakan "Args ..." di elipsis makro Anda untuk membuat struktur cek dengan arg template templat variadic. misalnya. Deteksi metode "void onNext (const T &)" HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... ); ...template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
ACyclic
4

Tidak ada yang aneh menyarankan trik bagus berikut yang pernah saya lihat di situs ini:

template <class T>
struct has_foo
{
    struct S { void foo(...); };
    struct derived : S, T {};

    template <typename V, V> struct W {};

    template <typename X>
    char (&test(W<void (X::*)(), &X::foo> *))[1];

    template <typename>
    char (&test(...))[2];

    static const bool value = sizeof(test<derived>(0)) == 1;
};

Anda harus memastikan T adalah kelas. Tampaknya ambiguitas dalam pencarian foo adalah kegagalan substitusi. Saya membuatnya bekerja pada gcc, tidak yakin apakah itu standar.

Alexandre C.
sumber
3

Templat generik yang dapat digunakan untuk memeriksa apakah beberapa "fitur" didukung oleh jenis:

#include <type_traits>

template <template <typename> class TypeChecker, typename Type>
struct is_supported
{
    // these structs are used to recognize which version
    // of the two functions was chosen during overload resolution
    struct supported {};
    struct not_supported {};

    // this overload of chk will be ignored by SFINAE principle
    // if TypeChecker<Type_> is invalid type
    template <typename Type_>
    static supported chk(typename std::decay<TypeChecker<Type_>>::type *);

    // ellipsis has the lowest conversion rank, so this overload will be
    // chosen during overload resolution only if the template overload above is ignored
    template <typename Type_>
    static not_supported chk(...);

    // if the template overload of chk is chosen during
    // overload resolution then the feature is supported
    // if the ellipses overload is chosen the the feature is not supported
    static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value;
};

Templat yang memeriksa apakah ada metode fooyang kompatibel dengan tanda tangandouble(const char*)

// if T doesn't have foo method with the signature that allows to compile the bellow
// expression then instantiating this template is Substitution Failure (SF)
// which Is Not An Error (INAE) if this happens during overload resolution
template <typename T>
using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));

Contohnya

// types that support has_foo
struct struct1 { double foo(const char*); };            // exact signature match
struct struct2 { int    foo(const std::string &str); }; // compatible signature
struct struct3 { float  foo(...); };                    // compatible ellipsis signature
struct struct4 { template <typename T>
                 int    foo(T t); };                    // compatible template signature

// types that do not support has_foo
struct struct5 { void        foo(const char*); }; // returns void
struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double
struct struct7 { double      foo(      int *); }; // const char* can't be converted to int*
struct struct8 { double      bar(const char*); }; // there is no foo method

int main()
{
    std::cout << std::boolalpha;

    std::cout << is_supported<has_foo, int    >::value << std::endl; // false
    std::cout << is_supported<has_foo, double >::value << std::endl; // false

    std::cout << is_supported<has_foo, struct1>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct2>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct3>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct4>::value << std::endl; // true

    std::cout << is_supported<has_foo, struct5>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct6>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct7>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct8>::value << std::endl; // false

    return 0;
}

http://coliru.stacked-crooked.com/a/83c6a631ed42cea4

anton_rh
sumber
Apakah ada cara untuk has_foomemasukkan panggilan template ke is_supported. Apa yang saya ingin adalah untuk memanggil sesuatu seperti: std::cout << is_supported<magic.foo(), struct1>::value << std::endl;. Alasan untuk ini, saya ingin mendefinisikan has_foountuk setiap tanda tangan fungsi yang berbeda yang ingin saya periksa sebelum saya dapat memeriksa fungsinya?
CJCombrink
2

Bagaimana dengan solusi ini?

#include <type_traits>

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

template <typename U>
struct hasToString<U,
  typename std::enable_if<bool(sizeof(&U::toString))>::type
> : std::true_type { };
pengguna1095108
sumber
Gagal jika toStringkelebihan beban, seperti &U::toStringambigu.
Yakk - Adam Nevraumont
@ Yakk Saya pikir para pemain dapat memperbaiki masalah ini.
user1095108
2

Ada banyak jawaban di sini, tetapi saya gagal, untuk menemukan versi, yang melakukan pemesanan resolusi metode nyata , sementara tidak menggunakan fitur c ++ yang lebih baru (hanya menggunakan fitur c ++ 98).
Catatan: Versi ini diuji dan bekerja dengan vc ++ 2013, g ++ 5.2.0 dan kompilator onlline.

Jadi saya datang dengan versi, yang hanya menggunakan sizeof ():

template<typename T> T declval(void);

struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);

struct yes { char v[1]; };
struct no  { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};

template<typename T>
struct has_awesome_member {
 template<typename U> static yes_no<(sizeof((
   declval<U>().awesome_member(),fake_void()
  ))!=0)> check(int);
 template<typename> static no check(...);
 enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};


struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };

static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");

Demo langsung (dengan pemeriksaan tipe pengembalian yang diperluas dan penyelesaian vc ++ 2010): http://cpp.sh/5b2vs

Tidak ada sumber, karena saya sendiri yang membuatnya.

Saat menjalankan demo Langsung pada kompiler g ++, harap perhatikan bahwa ukuran array 0 diperbolehkan, artinya static_assert yang digunakan tidak akan memicu kesalahan kompiler, bahkan ketika gagal.
Solusi yang umum digunakan adalah mengganti 'typedef' di makro dengan 'extern'.

pengguna3296587
sumber
Tidak, tapi saya mendeklarasikannya sendiri dan tidak menggunakan rvalue (lihat bagian atas kode saya). Atau Anda bisa meyakinkan diri sendiri dan mencoba demo langsung dalam mode c ++ 98. PS: static_assert juga bukan c ++ 98, tetapi ada work-arounds (live demo)
user3296587
d'oh! merindukan itu. :-)
Ian Ni-Lewis
Pernyataan statis Anda tidak berfungsi. Anda perlu menggunakan ukuran array -1 daripada 0 (coba meletakkan static_assert(false);). Saya menggunakan ini sehubungan dengan CRTP di mana saya ingin menentukan apakah kelas turunan memiliki fungsi tertentu - yang ternyata tidak berfungsi, namun pernyataan Anda selalu berlalu. Saya kehilangan beberapa rambut untuk yang satu itu.
babi
Saya berasumsi Anda menggunakan g ++. Harap dicatat, bahwa gcc / g ++ memiliki ekstensi yang memungkinkan untuk array berukuran nol ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html )
user3296587
Bisakah Anda menulis ulang ini agar tidak membebani operator,? mis. pilih operator lain? Juga, hindari polusi namespace dengan apa pun selain has_awesome_member?
einpoklum
1

Berikut ini adalah versi saya yang menangani semua kemungkinan fungsi anggota berlebih dengan arity sewenang-wenang, termasuk fungsi anggota templat, mungkin dengan argumen default. Ini membedakan 3 skenario yang saling eksklusif ketika membuat panggilan fungsi anggota ke beberapa tipe kelas, dengan tipe arg yang diberikan: (1) valid, atau (2) ambigu, atau (3) tidak dapat berjalan. Contoh penggunaan:

#include <string>
#include <vector>

HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)

struct test
{
   void bar(int);
   void bar(double);
   void bar(int,double);

   template < typename T >
   typename std::enable_if< not std::is_integral<T>::value >::type
   bar(const T&, int=0){}

   template < typename T >
   typename std::enable_if< std::is_integral<T>::value >::type
   bar(const std::vector<T>&, T*){}

   template < typename T >
   int bar(const std::string&, int){}
};

Sekarang Anda dapat menggunakannya seperti ini:

int main(int argc, const char * argv[])
{
   static_assert( has_mem_bar<test>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
   static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
   static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
   static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
   static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");

   static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
   static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");

   static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
   static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");

   static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");

   return 0;
}

Berikut adalah kode, ditulis dalam c ++ 11, namun, Anda dapat dengan mudah port (dengan tweak kecil) ke non-c ++ 11 yang memiliki ekstensi typeof (misalnya gcc). Anda dapat mengganti makro HAS_MEM dengan milik Anda.

#pragma once

#if __cplusplus >= 201103

#include <utility>
#include <type_traits>

#define HAS_MEM(mem)                                                                                     \
                                                                                                     \
template < typename T >                                                                               \
struct has_mem_##mem                                                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  struct ambiguate_seed { char mem; };                                                               \
  template < typename U > struct ambiguate : U, ambiguate_seed {};                                   \
                                                                                                     \
  template < typename U, typename = decltype(&U::mem) > static constexpr no  test(int);              \
  template < typename                                 > static constexpr yes test(...);              \
                                                                                                     \
  static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ;         \
  typedef std::integral_constant<bool,value>    type;                                                \
};


#define HAS_MEM_FUN_CALL(memfun)                                                                         \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_valid_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_valid_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  template < typename U, bool = has_mem_##memfun<U>::value >                                         \
  struct impl                                                                                        \
  {                                                                                                  \
     template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
     struct test_result { using type = yes; };                                                       \
                                                                                                     \
     template < typename V > static constexpr typename test_result<V>::type test(int);               \
     template < typename   > static constexpr                            no test(...);               \
                                                                                                     \
     static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value;                    \
     using type = std::integral_constant<bool, value>;                                               \
  };                                                                                                 \
                                                                                                     \
  template < typename U >                                                                            \
  struct impl<U,false> : std::false_type {};                                                         \
                                                                                                     \
  static constexpr bool value = impl<T>::value;                                                      \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_ambiguous_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) >                                              \
{                                                                                                     \
  struct ambiguate_seed { void memfun(...); };                                                       \
                                                                                                     \
  template < class U, bool = has_mem_##memfun<U>::value >                                            \
  struct ambiguate : U, ambiguate_seed                                                               \
  {                                                                                                  \
    using ambiguate_seed::memfun;                                                                    \
    using U::memfun;                                                                                 \
  };                                                                                                 \
                                                                                                     \
  template < class U >                                                                               \
  struct ambiguate<U,false> : ambiguate_seed {};                                                     \
                                                                                                     \
  static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_viable_mem_fun_call_##memfun;                                                              \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_viable_mem_fun_call_##memfun< T(Args...) >                                                 \
{                                                                                                     \
  static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value                   \
                             or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value;              \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_no_viable_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) >                                             \
{                                                                                                     \
  static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value;             \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct result_of_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct result_of_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  using type = decltype(std::declval<T>().memfun(std::declval<Args>()...));                          \
};

#endif

Hui
sumber
1

Anda dapat melewati semua metaprogramming di C ++ 14, dan cukup tulis ini menggunakan fit::conditionaldari Fit library:

template<class T>
std::string optionalToString(T* x)
{
    return fit::conditional(
        [](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
        [](auto*) { return "toString not defined"; }
    )(x);
}

Anda juga dapat membuat fungsi langsung dari lambdas juga:

FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
    [](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
    [](auto*) -> std::string { return "toString not defined"; }
);

Namun, jika Anda menggunakan kompiler yang tidak mendukung lambda generik, Anda harus menulis objek fungsi terpisah:

struct withToString
{
    template<class T>
    auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
    {
        return obj->toString();
    }
};

struct withoutToString
{
    template<class T>
    std::string operator()(T*) const
    {
        return "toString not defined";
    }
};

FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
    withToString(),
    withoutToString()
);
Paul Fultz II
sumber
1
Seberapa mudah untuk menulis ini agar tidak harus bergantung pada fitatau perpustakaan lain selain standar?
einpoklum
1

Dengan C ++ 20 Anda dapat menulis yang berikut ini:

template<typename T>
concept has_toString = requires(const T& t) {
    t.toString();
};

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}
Bernd Baumanns
sumber
0

Ini adalah contoh dari kode yang berfungsi.

template<typename T>
using toStringFn = decltype(std::declval<const T>().toString());

template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
    return obj->toString();
}

template <class T>
std::string optionalToString(const T* obj, long)
{
    return "toString not defined";
}

int main()
{
    A* a;
    B* b;

    std::cout << optionalToString(a, 0) << std::endl; // This is A
    std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}

toStringFn<T>* = nullptrakan mengaktifkan fungsi yang membutuhkan intargumen ekstra yang memiliki prioritas lebih dari fungsi yang dibutuhkan longsaat dipanggil dengan 0.

Anda dapat menggunakan prinsip yang sama untuk fungsi yang kembali truejika fungsi diimplementasikan.

template <typename T>
constexpr bool toStringExists(long)
{
    return false;
}

template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
    return true;
}


int main()
{
    A* a;
    B* b;

    std::cout << toStringExists<A>(0) << std::endl; // true
    std::cout << toStringExists<B>(0) << std::endl; // false
}
tereshkd
sumber
0

Saya punya masalah serupa:

Kelas templat yang mungkin diturunkan dari beberapa kelas dasar, beberapa yang memiliki anggota tertentu dan yang lain tidak.

Saya memecahkannya mirip dengan jawaban "typeof" (Nicola Bonelli), tetapi dengan decltype sehingga ia mengkompilasi dan berjalan dengan benar di MSVS:

#include <iostream>
#include <string>

struct Generic {};    
struct HasMember 
{
  HasMember() : _a(1) {};
  int _a;
};    

// SFINAE test
template <typename T>
class S : public T
{
public:
  std::string foo (std::string b)
  {
    return foo2<T>(b,0);
  }

protected:
  template <typename T> std::string foo2 (std::string b, decltype (T::_a))
  {
    return b + std::to_string(T::_a);
  }
  template <typename T> std::string foo2 (std::string b, ...)
  {
    return b + "No";
  }
};

int main(int argc, char *argv[])
{
  S<HasMember> d1;
  S<Generic> d2;

  std::cout << d1.foo("HasMember: ") << std::endl;
  std::cout << d2.foo("Generic: ") << std::endl;
  return 0;
}
Yigal Eilam
sumber
0

Satu lagi cara untuk melakukannya di C ++ 17 (terinspirasi oleh boost: hana).

Tulis satu kali dan gunakan berkali-kali. Itu tidak memerlukan has_something<T>kelas tipe ciri.

#include <type_traits>

template<typename T, typename F>
constexpr auto is_valid(F&& f) -> decltype(f(std::declval<T>()), true) { return true; }

template<typename>
constexpr bool is_valid(...) { return false; }

#define IS_VALID(T, EXPR) is_valid<T>( [](auto&& obj)->decltype(obj.EXPR){} )

Contoh

#include <iostream>

struct Example {
    int Foo;
    void Bar() {}
    std::string toString() { return "Hello from toString()!"; }
};

struct Example2 {
    int X;
};

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr(IS_VALID(T, toString()))
        return obj->toString();
    else
        return "toString not defined";
}

int main() {
    static_assert(IS_VALID(Example, Foo));
    static_assert(IS_VALID(Example, Bar()));
    static_assert(!IS_VALID(Example, ZFoo));
    static_assert(!IS_VALID(Example, ZBar()));

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}
Dmytro Ovdiienko
sumber
-1
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
     return obj->toString();
}

template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
     throw "Error!";
}
Abhishek
sumber
6
"Kami tidak memerlukan deskripsi jawaban" ... harap tambahkan deskripsi informatif pada jawaban Anda untuk memperbaikinya. Terima kasih.
YesThatIsMyName