Pertanyaan Bagaimana cara memanggil fungsi templated jika ada, dan sesuatu yang lain sebaliknya?


Saya ingin melakukan sesuatu seperti

template <typename T>
void foo(const T& t) {
   IF bar(t) would compile
      bar(t);
   ELSE
      baz(t);
}

Saya berpikir bahwa sesuatu menggunakan enable_if akan melakukan pekerjaan di sini, berpisah foo menjadi dua bagian, tetapi saya tidak bisa mengetahui detailnya. Apa cara paling sederhana untuk mencapai ini?


32
2017-09-06 17:23


asal


Jawaban:


Ada dua pencarian yang dilakukan untuk nama itu bar. Salah satunya adalah pencarian yang tidak memenuhi syarat pada konteks definisi foo. Yang lainnya adalah pencarian tergantung argumen pada setiap konteks instantiasi (tetapi hasil dari pencarian pada setiap konteks instantiasi tidak diizinkan untuk mengubah perilaku antara dua konteks instantiasi yang berbeda).

Untuk mendapatkan perilaku yang diinginkan, Anda bisa pergi dan menentukan fungsi fallback di a fallback namespace yang mengembalikan beberapa tipe unik

namespace fallback {
  // sizeof > 1
  struct flag { char c[2]; };
  flag bar(...);
}

Itu bar fungsi akan dipanggil jika tidak ada yang cocok karena elipsis memiliki biaya konversi terburuk. Sekarang, sertakan bahwa kandidat ke dalam fungsi Anda dengan menggunakan arahan fallback, maka fallback::bar disertakan sebagai kandidat dalam panggilan ke bar.

Sekarang, untuk melihat apakah panggilan ke bar menyelesaikan ke fungsi Anda, Anda akan menyebutnya, dan periksa apakah jenis kembalinya adalah flag. Jenis kembalinya fungsi yang dipilih sebaliknya dapat batal, jadi Anda harus melakukan beberapa trik operator koma untuk mengatasinya.

namespace fallback {
  int operator,(flag, flag);

  // map everything else to void
  template<typename T> 
  void operator,(flag, T const&);

  // sizeof 1
  char operator,(int, flag);
}

Jika fungsi kami dipilih maka permintaan operator koma akan mengembalikan referensi int. Jika tidak atau jika fungsi yang dipilih kembali void, maka pengembalian permintaan void gantinya. Kemudian doa selanjutnya dengan flag sebagai argumen kedua akan mengembalikan tipe yang memiliki ukuran 1 jika fallback kami dipilih, dan sizeof yang lebih besar 1 (operator koma built-in akan digunakan karena void dalam campuran) jika sesuatu yang lain dipilih.

Kami membandingkan sizeof dan mendelegasikan ke struct.

template<bool>
struct foo_impl;

/* bar available */
template<>
struct foo_impl<true> {
  template<typename T>
  static void foo(T const &t) {
    bar(t);
  }
};

/* bar not available */
template<>
struct foo_impl<false> {
  template<typename T>
  static void foo(T const&) {
    std::cout << "not available, calling baz...";
  }
};

template <typename T>
void foo(const T& t) {
   using namespace fallback;

   foo_impl<sizeof (fallback::flag(), bar(t), fallback::flag()) != 1>
     ::foo(t);
}

Solusi ini ambigu jika fungsi yang ada memiliki elipsis juga. Tapi sepertinya itu agak tidak mungkin. Uji menggunakan fallback:

struct C { };
int main() {
  // => "not available, calling baz..."
  foo(C());
}

Dan jika seorang kandidat ditemukan menggunakan pencarian tergantung argumen

struct C { };
void bar(C) {
  std::cout << "called!";
}
int main() {
  // => "called!"
  foo(C());
}

Untuk menguji pencarian yang tidak memenuhi syarat pada konteks definisi, mari kita definisikan fungsi berikut di atas foo_impl dan foo (letakkan template foo_impl di atas foo, jadi mereka memiliki konteks definisi yang sama)

void bar(double d) {
  std::cout << "bar(double) called!";
}

// ... foo template ...

int main() {
  // => "bar(double) called!"
  foo(12);
}

31
2017-09-06 18:49



litb telah memberi Anda jawaban yang sangat bagus. Namun, saya bertanya-tanya apakah, mengingat lebih banyak konteks, kita tidak bisa menghasilkan sesuatu yang kurang umum, tetapi juga kurang, um, rumit?

Misalnya, jenis apa yang bisa T? Apa pun? Beberapa tipe? Satu set yang sangat terbatas yang dapat Anda kendalikan? Beberapa kelas yang Anda desain bersama dengan fungsi foo? Mengingat yang terakhir, Anda bisa meletakkan sesuatu seperti sederhana

typedef boolean<true> has_bar_func;

ke dalam jenis dan kemudian beralih ke yang berbeda foo kelebihan beban berdasarkan itu:

template <typename T>
void foo_impl(const T& t, boolean<true> /*has_bar_func*/);
template <typename T>
void foo_impl(const T& t, boolean<false> /*has_bar_func*/);

template <typename T>
void foo(const T& t) {
  foo_impl( t, typename T::has_bar_func() );
}

Juga, dapat bar/baz fungsi hanya tentang tanda tangan, apakah ada set yang agak terbatas, atau hanya ada satu tanda tangan yang valid? Jika yang terakhir, ide fallback litb (yang luar biasa), dalam hubungannya dengan penggunaan meta-fungsi sizeofmungkin sedikit lebih sederhana. Tapi ini saya belum dieksplorasi, jadi itu hanya sebuah pemikiran.


6
2017-09-06 19:53



Saya pikir solusi litb berfungsi, tetapi terlalu rumit. Alasannya adalah dia memperkenalkan fungsi fallback::bar(...) yang bertindak sebagai "fungsi pilihan terakhir", dan kemudian pergi ke luar biasa untuk tidak menyebutnya. Mengapa? Sepertinya kita memiliki perilaku yang sempurna untuk itu:

namespace fallback {
    template<typename T>
    inline void bar(T const& t, ...)
    {
        baz(t);
    }
}
template<typename T>
void foo(T const& t)
{
    using namespace fallback;
    bar(t);
}

Tapi seperti yang saya tunjukkan dalam komentar untuk posting asli litb, ada banyak alasan mengapa bar(t) gagal dikompilasi, dan saya tidak yakin solusi ini menangani kasus yang sama. Ini pasti akan gagal pada private bar::bar(T t)


3
2017-09-07 10:48



Jika Anda bersedia membatasi diri Anda untuk Visual C ++, Anda dapat menggunakan __if_exists dan __if_not_exists pernyataan.

Berguna dalam keadaan darurat, tetapi platform spesifik.


2
2017-09-06 19:13



EDIT: Saya berbicara terlalu cepat! jawaban litb menunjukkan bagaimana hal ini sebenarnya dapat dilakukan (dengan kemungkinan biaya kewarasan Anda ... :-P)

Sayangnya saya pikir kasus umum pengecekan "akan mengkompilasi ini" berada di luar jangkauan deduksi argumen template fungsi + SFINAE, yang merupakan tipuan biasa untuk hal ini. Saya pikir yang terbaik yang dapat Anda lakukan adalah membuat template fungsi "cadangan":

template <typename T>
void bar(T t) {   // "Backup" bar() template
    baz(t);
}

Dan kemudian berubah foo() untuk hanya:

template <typename T>
void foo(const T& t) {
    bar(t);
}

Ini akan berfungsi untuk sebagian besar kasus. Karena bar() tipe parameter template adalah T, itu akan dianggap "kurang khusus" jika dibandingkan dengan fungsi lain atau template fungsi bernama bar() dan karena itu akan menyerahkan prioritas ke fungsi atau fungsi template yang sudah ada selama resolusi overload. Kecuali itu:

  • Jika sudah ada sebelumnya bar() itu sendiri adalah template fungsi yang mengambil parameter template tipe T, ambiguitas akan muncul karena tidak ada template yang lebih khusus daripada yang lain, dan kompiler akan mengeluh.
  • Konversi implisit juga tidak akan berfungsi, dan akan menyebabkan kesulitan untuk mendiagnosis masalah: Anggaplah ada yang sudah ada sebelumnya bar(long) tapi foo(123) disebut. Dalam hal ini, kompilator akan diam-diam memilih untuk instantiate "cadangan" bar() template dengan T = int alih-alih melakukan int->long promosi, meskipun yang terakhir akan dikompilasi dan bekerja dengan baik!

Singkatnya: tidak ada solusi yang mudah dan lengkap, dan saya cukup yakin bahkan tidak ada solusi yang rumit dan menyeluruh. :(


2
2017-09-06 18:23



//default

////////////////////////////////////////// 
    template <class T>
    void foo(const T& t){
        baz(t);
    }

//specializations
//////////////////////////////////////////  

    template <>
    void foo(const specialization_1& t){
        bar(t);
    }
    ....
    template <>
    void foo(const specialization_n& t){
        bar(t);
    }

2
2017-09-06 21:01



Apakah Anda tidak dapat menggunakan spesialisasi penuh di sini (atau terlalu banyak) di foo. Dengan mengatakan memiliki fungsi bar panggilan template tetapi untuk jenis tertentu sepenuhnya mengkhususkan untuk memanggil baz?


0
2017-09-06 17:32