Spesialisasi parsial template fungsi C ++?


91

Saya tahu bahwa kode di bawah ini adalah spesialisasi sebagian dari sebuah kelas:

template <typename T1, typename T2> 
class MyClass { 
  … 
}; 


// partial specialization: both template parameters have same type 
template <typename T> 
class MyClass<T,T> { 
  … 
}; 

Saya juga tahu bahwa C ++ tidak mengizinkan spesialisasi sebagian template fungsi (hanya penuh yang diperbolehkan). Tetapi apakah kode saya berarti bahwa saya telah mengkhususkan sebagian template fungsi saya untuk satu / jenis argumen yang sama? Karena berfungsi untuk Microsoft Visual Studio 2010 Express! Jika tidak, dapatkah Anda menjelaskan konsep spesialisasi parsial?

#include <iostream>
using std::cin;
using std::cout;
using std::endl;

template <typename T1, typename T2> 
inline T1 max (T1 const& a, T2 const& b) 
{ 
    return a < b ? b : a; 
} 

template <typename T> 
inline T const& max (T const& a, T const& b)
{
    return 10;
}


int main ()
{
    cout << max(4,4.2) << endl;
    cout << max(5,5) << endl;
    int z;
    cin>>z;
}

Carilah analogi spesialisasi kelas itu. Jika disebut spesialisasi kelas, lalu mengapa saya harus mempertimbangkan hal yang sama untuk fungsi sebagai kelebihan beban ??
Narek

1
Tidak, sintaks spesialisasi berbeda. Lihatlah sintaks spesialisasi fungsi (seharusnya) dalam jawaban saya di bawah ini.
iammilind

2
Mengapa ini tidak memunculkan kesalahan "Panggilan ke maks. Ambigius"? Bagaimana max(5,5)menyelesaikannya max(T const&, T const&) [with T=int]dan tidak max(T1 const&, T2 const&) [with T1=int and T2=int]?
NHDaly

Jawaban:


83

Spesialisasi parsial fungsi belum diperbolehkan sesuai standar. Dalam contoh ini, Anda benar-benar overloading & tidak mengkhususkan diri pada max<T1,T2>fungsi.
Its sintaks harus melihat agak seperti di bawah ini, telah itu telah diizinkan:

// Partial specialization is not allowed by the spec, though!
template <typename T> 
inline T const& max<T,T> (T const& a, T const& b)
{                  ^^^^^ <--- [supposed] specializing here
  return 10;
}

Dalam kasus template fungsi, hanya spesialisasi penuh yang diperbolehkan oleh standar C ++, - tidak termasuk ekstensi compiler!


1
@Narek, Spesialisasi fungsi parsial bukan bagian dari standar (untuk alasan apapun). Saya pikir MSVC mendukungnya sebagai ekstensi. Mungkin setelah beberapa saat, itu akan diizinkan oleh kompiler lain juga.
iammilind

1
@ iammilind: Tidak masalah. Dia sepertinya sudah tahu itu. Itulah mengapa dia mencobanya untuk template fungsi juga. Jadi saya mengeditnya lagi, membuatnya jelas sekarang.
Nawaz

22
Adakah yang bisa menjelaskan mengapa spesialisasi parsial tidak diizinkan?
HelloGoodbye

2
@ NHDaly, Ini tidak memberikan kesalahan ambiguitas karena 1 fungsi lebih cocok daripada yang lain. Mengapa memilih (T, T)over (T1, T2)for (int, int), adalah karena yang pertama menjamin bahwa ada 2 parameter dan kedua tipe adalah sama; yang terakhir hanya menjamin bahwa ada 2 parameter. Penyusun selalu memilih deskripsi yang akurat. mis. Jika Anda harus membuat pilihan antara 2 deskripsi "River", mana yang akan Anda pilih? "kumpulan air" vs "kumpulan air yang mengalir".
iammilind

1
@kfsone, saya pikir fitur ini sedang ditinjau, karenanya terbuka untuk interpretasi. Anda dapat merujuk bagian open-std ini , yang saya lihat di Mengapa standar C ++ tidak mengizinkan spesialisasi parsial template fungsi?
iammilind

44

Karena spesialisasi parsial tidak diperbolehkan - seperti yang ditunjukkan oleh jawaban lain -, Anda dapat mengatasinya dengan menggunakan std::is_samedan std::enable_if, seperti di bawah ini:

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, int>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with ints! " << f << std::endl;
}

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, float>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with floats! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
}

Keluaran:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2

Sunting : Jika Anda ingin menangani semua kasus lain yang tersisa, Anda dapat menambahkan definisi yang menyatakan bahwa kasus yang sudah ditangani tidak boleh cocok - jika tidak, Anda akan jatuh ke dalam definisi yang ambigu. Definisi tersebut dapat berupa:

template <typename T, class F>
inline typename std::enable_if<(not std::is_same<T, int>::value)
    and (not std::is_same<T, float>::value), void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with unknown stuff! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
    typed_foo<std::string>("either");
}

Yang menghasilkan:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2
>>> messing with unknown stuff! either

Meskipun hal semua kasus ini terlihat agak membosankan, karena Anda harus memberi tahu compiler semua yang telah Anda lakukan, cukup dapat dilakukan untuk menangani hingga 5 atau beberapa spesialisasi lagi.


Sebenarnya tidak perlu melakukan ini karena ini dapat ditangani dengan kelebihan beban fungsi dengan cara yang lebih sederhana dan lebih jelas.
Adrian

2
@Adrian Saya benar-benar tidak dapat memikirkan pendekatan overloading fungsi lain untuk menyelesaikan ini. Anda melihat kelebihan muatan sebagian tidak diperbolehkan, bukan? Bagikan dengan kami solusi Anda, jika menurut Anda lebih jelas.
Rubens

1
apakah ada cara lain untuk menangkap semua fungsi template dengan mudah ?
Nick

15

Apa itu spesialisasi?

Jika Anda benar-benar ingin memahami template, Anda harus melihat bahasa fungsional. Dunia template di C ++ adalah subbahasanya sendiri yang berfungsi murni.

Dalam bahasa fungsional, pemilihan dilakukan menggunakan Pencocokan Pola :

-- An instance of Maybe is either nothing (None) or something (Just a)
-- where a is any type
data Maybe a = None | Just a

-- declare function isJust, which takes a Maybe
-- and checks whether it's None or Just
isJust :: Maybe a -> Bool

-- definition: two cases (_ is a wildcard)
isJust None = False
isJust Just _ = True

Seperti yang Anda lihat, kami membebani definisi isJust.

Nah, template kelas C ++ bekerja dengan cara yang persis sama. Anda memberikan deklarasi utama , yang menyatakan jumlah dan sifat parameter. Ini bisa berupa deklarasi, atau juga bertindak sebagai definisi (pilihan Anda), dan kemudian Anda dapat (jika Anda mau) memberikan spesialisasi pola dan mengaitkannya dengan mereka versi kelas yang berbeda (jika tidak maka akan konyol). .

Untuk fungsi templat, spesialisasi agak lebih aneh: ini agak bertentangan dengan resolusi yang berlebihan. Dengan demikian, telah diputuskan bahwa spesialisasi akan terkait dengan versi non-khusus, dan spesialisasi tidak akan dipertimbangkan selama resolusi kelebihan beban. Oleh karena itu, algoritma pemilihan fungsi yang tepat menjadi:

  1. Lakukan resolusi kelebihan beban, di antara fungsi biasa dan templat non-khusus
  2. Jika template tidak terspesialisasi dipilih, periksa apakah ada spesialisasi untuk itu yang akan lebih cocok

(untuk perawatan mendalam, lihat GotW # 49 )

Dengan demikian, spesialisasi templat fungsi adalah warga zona kedua (secara harfiah). Sejauh yang saya ketahui, kita akan lebih baik tanpanya: Saya belum pernah menemukan kasus di mana penggunaan spesialisasi template tidak dapat diselesaikan dengan kelebihan beban.

Apakah ini spesialisasi template?

Tidak, ini hanya kelebihan beban, dan ini bagus. Faktanya, kelebihan beban biasanya bekerja seperti yang kita harapkan, sementara spesialisasi bisa mengejutkan (ingat artikel GotW yang saya tautkan).


"As such, template specialization of functions is a second-zone citizen (literally). As far as I am concerned, we would be better off without them: I have yet to encounter a case where a template specialization use could not be solved with overloading instead."Bagaimana dengan parameter template non tipe?
Jules GM

@Julius: Anda masih bisa menggunakan overloading, meskipun dengan memasukkan parameter tiruan seperti boost::mpl::integral_c<unsigned, 3u>. Solusi lain juga bisa dengan menggunakan enable_if/ disable_if, meskipun ceritanya berbeda.
Matthieu M.

8

Spesialisasi parsial non-kelas dan non-variabel tidak diperbolehkan, tetapi seperti yang dikatakan:

Semua masalah dalam ilmu komputer dapat diselesaikan dengan tingkat tipuan lain. —— David Wheeler

Menambahkan kelas untuk meneruskan pemanggilan fungsi dapat menyelesaikan ini, berikut adalah contohnya:

template <class Tag, class R, class... Ts>
struct enable_fun_partial_spec;

struct fun_tag {};

template <class R, class... Ts>
constexpr R fun(Ts&&... ts) {
  return enable_fun_partial_spec<fun_tag, R, Ts...>::call(
      std::forward<Ts>(ts)...);
}

template <class R, class... Ts>
struct enable_fun_partial_spec<fun_tag, R, Ts...> {
  constexpr static R call(Ts&&... ts) { return {0}; }
};

template <class R, class T>
struct enable_fun_partial_spec<fun_tag, R, T, T> {
  constexpr static R call(T, T) { return {1}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, int> {
  constexpr static R call(int, int) { return {2}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, char> {
  constexpr static R call(int, char) { return {3}; }
};

template <class R, class T2>
struct enable_fun_partial_spec<fun_tag, R, char, T2> {
  constexpr static R call(char, T2) { return {4}; }
};

static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, "");
static_assert(fun<int>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, "");
static_assert(fun<char>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, "");
static_assert(fun<long>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, "");
static_assert(fun<double>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, "");
static_assert(fun<int>(1u, 1) == 0, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 'c')), char>, "");
static_assert(fun<char>(1, 'c') == 3, "");

static_assert(std::is_same_v<decltype(fun<unsigned>('c', 1)), unsigned>, "");
static_assert(fun<unsigned>('c', 1) == 4, "");

static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, "");
static_assert(fun<unsigned>(10.0, 1) == 0, "");

static_assert(
    std::is_same_v<decltype(fun<double>(1, 2, 3, 'a', "bbb")), double>, "");
static_assert(fun<double>(1, 2, 3, 'a', "bbb") == 0, "");

static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, "");
static_assert(fun<unsigned>() == 0, "");

4

Tidak. Misalnya, Anda dapat mengkhususkan secara hukum std::swap, tetapi Anda tidak dapat secara hukum menentukan kelebihan beban Anda sendiri. Itu berarti Anda tidak dapat membuat std::swappekerjaan untuk template kelas kustom Anda sendiri.

Spesialisasi yang berlebihan dan parsial dapat memiliki efek yang sama dalam beberapa kasus, tetapi jauh dari semuanya.


4
Itulah mengapa Anda menempatkan swapkelebihan Anda di namespace Anda.
jpalecek

2

Jawaban terlambat, tetapi beberapa pembaca yang terlambat mungkin menganggapnya berguna: Kadang-kadang, fungsi penolong - yang dirancang sedemikian rupa sehingga dapat dikhususkan - dapat menyelesaikan masalah juga.

Jadi mari kita bayangkan, inilah yang kami coba selesaikan:

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = new R(x);
    f(r, y); // another template function?
}

// for some reason, we NEED the specialization:
template <typename R, typename Y>
void function<R, int, Y>(int x, Y y) 
{
    // unfortunately, Wrapper has no constructor accepting int:
    Wrapper* w = new Wrapper();
    w->setValue(x);
    f(w, y);
}

Oke, spesialisasi fungsi template parsial, kita tidak bisa melakukan itu ... Jadi mari kita "mengekspor" bagian yang diperlukan untuk spesialisasi ke dalam fungsi pembantu, mengkhususkan yang satu itu dan menggunakannya:

template <typename R, typename T>
R* create(T t)
{
    return new R(t);
}
template <>
Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal...
{
    Wrapper* w = new Wrapper();
    w->setValue(n);
    return w;
}

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = create<R>(x);
    f(r, y); // another template function?
}

Ini bisa menjadi menarik terutama jika alternatifnya (kelebihan beban normal daripada spesialisasi, solusi yang diusulkan oleh Rubens, ... - bukan berarti ini buruk atau milik saya lebih baik, hanya satu lagi ) akan berbagi cukup banyak kode umum.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.