C ++ işlev şablonu kısmi özelleştirme?


91

Aşağıdaki kodun bir sınıfın kısmi bir uzmanlığı olduğunu biliyorum:

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


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

Ayrıca, C ++ 'nın işlev şablonu kısmi uzmanlaşmasına izin vermediğini biliyorum (yalnızca tam olarak izin verilir). Ancak kodum, işlev şablonumu bir / aynı tür bağımsız değişkenler için kısmen özelleştirdiğim anlamına mı geliyor? Çünkü Microsoft Visual Studio 2010 Express için çalışıyor! Hayır ise, lütfen kısmi uzmanlaşma kavramını açıklar mısınız?

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

Bu sınıf uzmanlığı analojisine bakın. Sınıf uzmanlaşması deniliyorsa, neden işlev için aşırı yükleme olarak aynı şeyi düşünmeliyim?
Narek

1
Hayır, uzmanlık sözdizimi farklıdır. Aşağıdaki cevabımdaki (varsayılan) işlev uzmanlığı sözdizimine bakın.
iammilind

2
Bu neden "Max'e Çağrı belirsizdir" hatası vermiyor? Nasıl max(5,5)çözülür max(T const&, T const&) [with T=int]ve çözülmez max(T1 const&, T2 const&) [with T1=int and T2=int]?
NHDaly

Yanıtlar:


83

Standart gereği kısmi işlev uzmanlaşmasına henüz izin verilmiyor. Örnekte, aslında vardır aşırı & uzmanlaşmış değilmax<T1,T2> işlevi.
Onun sözdizimi bakmalıydım biraz daha izin verilseydi, aşağıda gibi:

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

Bir işlev şablonları durumunda , C ++ standardı tarafından yalnızca tam uzmanlaşmaya izin verilir - derleyici uzantıları hariç!


1
@Narek, Kısmi işlev uzmanlığı standardın bir parçası değildir (herhangi bir nedenle). MSVC'nin bir uzantı olarak desteklediğini düşünüyorum. Bir süre sonra olabilir, diğer derleyiciler tarafından da izin verilebilir.
iammilind

1
@iammilind: Sorun değil. Bunu zaten biliyor gibi görünüyor. Bu yüzden bunu işlev şablonu için de deniyor. Bu yüzden tekrar düzenledim, şimdi netleştirdim.
Nawaz

22
Kısmi uzmanlaşmaya neden izin verilmediğini açıklayabilen var mı?
HelloGoodbye

2
@NHDaly, 1 işlev diğerinden daha iyi eşleştiği için belirsizlik hatası vermez. O seçer Neden (T, T)üzerinde (T1, T2)için (int, int)eski garantiler 2 parametre olduğunu ve her iki tip aynı olduğu için, olduğu; ikincisi yalnızca 2 parametrenin olduğunu garanti eder. Derleyici her zaman doğru bir tanım seçer. Örneğin, "Nehir" in 2 tanımı arasında seçim yapmanız gerekirse, hangisini seçerdiniz? "su toplama" ve "akan suyun toplanması".
iammilind

1
@kfsone, sanırım bu özellik inceleniyor, dolayısıyla yoruma açık. C ++ standardı neden işlev şablonu kısmi uzmanlaşmasına izin vermiyor? Bölümünde gördüğüm bu açık standart bölüme başvurabilirsiniz ?
iammilind

44

Kısmi uzmanlaşmaya izin verilmediğinden - diğer yanıtların da işaret ettiği gibi -, aşağıdaki gibi std::is_sameve kullanarak bu konuda çalışabilirsiniz std::enable_if:

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

Çıktı:

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

Düzenleme : Kalan diğer tüm vakaları tedavi edebilmeniz gerekirse, önceden tedavi edilmiş vakaların eşleşmemesi gerektiğini belirten bir tanım ekleyebilirsiniz - aksi takdirde belirsiz tanımlara düşersiniz. Tanım şöyle olabilir:

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

Hangi üretir:

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

Bu halde her olgu zaten yaptık derleyici her şeyi söylemek zorunda çünkü şey, biraz sıkıcı görünüyor, 5 veya birkaç uzmanlık kadar tedavi etmek oldukça başarmak mümkün.


Gerçekten bunu yapmaya gerek yoktur, çünkü bu çok daha basit ve daha net bir şekilde fonksiyon aşırı yükleme ile halledilebilir.
Adrian

2
@Adrian Bunu çözmek için başka bir fonksiyon aşırı yükleme yaklaşımı düşünemiyorum. Kısmi aşırı yüklemeye izin verilmediğini fark ettiniz, değil mi? Daha net olduğunu düşünüyorsanız, çözümünüzü bizimle paylaşın.
Rubens

1
Tüm şablonlu işlevleri kolayca yakalamanın başka bir yolu var mı?
Nick

15

Uzmanlık nedir?

Şablonları gerçekten anlamak istiyorsanız, işlevsel dillere bir göz atmalısınız. C ++ 'daki şablonlar dünyası, kendi başına tamamen işlevsel bir alt dildir.

İşlevsel dillerde, seçimler Kalıp Eşleştirme kullanılarak yapılır :

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

Gördüğünüz gibi , tanımını aşırı yüklüyoruzisJust .

C ++ sınıf şablonları tamamen aynı şekilde çalışır. Parametrelerin sayısını ve niteliğini belirten bir ana bildirim sağlarsınız. Bu sadece bir bildirim olabilir veya bir tanım olarak da hareket edebilir (seçiminiz) ve sonra (isterseniz) modelin uzmanlıklarını sağlayabilir ve bunlarla sınıfın farklı (aksi halde aptalca) bir versiyonunu ilişkilendirebilirsiniz. .

Şablon işlevleri için, uzmanlaşma biraz daha tuhaftır: aşırı yük çözünürlüğü ile bir şekilde çelişir. Bu nedenle, bir uzmanlığın uzmanlaşmamış bir sürümle ilgili olacağına ve aşırı yük çözümü sırasında uzmanlıkların dikkate alınmayacağına karar verildi. Bu nedenle, doğru işlevi seçme algoritması şu hale gelir:

  1. Normal işlevler ve özel olmayan şablonlar arasında aşırı yük çözümü gerçekleştirin
  2. Uzmanlaşmamış bir şablon seçilirse, bunun için daha iyi bir eşleşme olacak bir uzmanlık olup olmadığını kontrol edin

(derinlemesine tedavi için bkz. GotW # 49 )

Bu nedenle, işlevlerin şablon uzmanlığı ikinci bölge vatandaşıdır (kelimenin tam anlamıyla). Bana kalırsa, onlarsız daha iyi oluruz: Şablon uzmanlık kullanımının bunun yerine aşırı yükleme ile çözülemediği bir durumla henüz karşılaşmadım.

Bu bir şablon uzmanlığı mı?

Hayır, bu sadece bir aşırı yük ve bu sorun değil. Aslında, aşırı yüklemeler genellikle beklediğimiz gibi çalışır, uzmanlıklar şaşırtıcı olabilir (bağlantılandırdığım GotW makalesini hatırlayın).


"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."Tip dışı şablon parametrelerine ne dersiniz?
Jules GM

@Julius: gibi kukla bir parametre ekleyerek de aşırı yüklemeyi kullanabilirsiniz boost::mpl::integral_c<unsigned, 3u>. Başka bir çözüm de enable_if/ kullanmak olabilir disable_if, ancak bu farklı bir hikaye.
Matthieu M.

8

Sınıf dışı, değişken olmayan kısmi uzmanlaşmaya izin verilmez, ancak söylendiği gibi:

Bilgisayar bilimindeki tüm problemler, başka bir dolaylı yolla çözülebilir. —— David Wheeler

İşlev çağrısını iletmek için bir sınıf eklemek bunu çözebilir, işte bir örnek:

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

Hayır. Örneğin, yasal olarak uzmanlaşabilirsiniz std::swap, ancak kendi aşırı yükünüzü yasal olarak tanımlayamazsınız. Bu std::swap, kendi özel sınıf şablonunuz için çalışamayacağınız anlamına gelir .

Aşırı yükleme ve kısmi uzmanlaşma bazı durumlarda aynı etkiye sahip olabilir, ancak hepsinden uzaktır.


4
Bu yüzden swapaşırı yükünüzü ad alanınıza koyuyorsunuz .
jpalecek

2

Geç cevap, ancak bazı geç okuyucular bunu yararlı bulabilir: Bazen, özelleşebilecek şekilde tasarlanmış yardımcı bir işlev de sorunu çözebilir.

Hayal edelim, çözmeye çalıştığımız şey bu :

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

Tamam, kısmi şablon işlevi uzmanlığı, bunu yapamayız ... Öyleyse uzmanlaşma için gereken parçayı bir yardımcı işleve "dışa aktaralım", onu özelleştirip kullanalım:

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?
}

Bu , özellikle alternatifler (uzmanlıklar yerine normal aşırı yüklemeler, Rubens tarafından önerilen geçici çözüm , ... - bunların kötü olduğu veya benimki daha iyi olduğu değil, sadece bir başkası ) oldukça fazla ortak kodu paylaşıyorsa ilginç olabilir .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.