เทมเพลตฟังก์ชัน C ++ เฉพาะบางส่วน?


91

ฉันรู้ว่าโค้ดด้านล่างนี้เป็นความเชี่ยวชาญเฉพาะบางส่วนของคลาส:

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


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

นอกจากนี้ฉันรู้ว่า C ++ ไม่อนุญาตความเชี่ยวชาญเฉพาะบางส่วนของเทมเพลตฟังก์ชัน (อนุญาตให้ใช้แบบเต็มเท่านั้น) แต่รหัสของฉันหมายความว่าฉันมีความเชี่ยวชาญเฉพาะบางส่วนของเทมเพลตฟังก์ชันสำหรับอาร์กิวเมนต์ประเภทเดียว / ประเภทเดียวกันหรือไม่ เพราะมันใช้งานได้กับ Microsoft Visual Studio 2010 Express! ถ้าไม่คุณช่วยอธิบายแนวคิดความเชี่ยวชาญเฉพาะบางส่วนได้ไหม

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

มองหาความคล้ายคลึงของความเชี่ยวชาญในชั้นเรียน ถ้าเรียกว่า class specialization แล้วทำไมฉันถึงต้องพิจารณาสิ่งเดียวกันกับ function ว่า overloading ??
Narek

1
ไม่ไวยากรณ์ความเชี่ยวชาญแตกต่างกัน ดูไวยากรณ์ความเชี่ยวชาญของฟังก์ชัน (ควร) ในคำตอบของฉันด้านล่าง
iammilind

2
เหตุใดจึงไม่เกิดข้อผิดพลาด "Call to max is ambigious" วิธีการmax(5,5)แก้ไขmax(T const&, T const&) [with T=int]และไม่max(T1 const&, T2 const&) [with T1=int and T2=int]?
NHDaly

คำตอบ:


83

ยังไม่อนุญาตให้ใช้ฟังก์ชันเฉพาะบางส่วนตามมาตรฐาน ในตัวอย่างคุณทำงานหนักเกินไปและไม่เชี่ยวชาญในmax<T1,T2>ฟังก์ชันนี้ ไวยากรณ์
ของมันควรมีลักษณะค่อนข้างเหมือนด้านล่างหากได้รับอนุญาต:

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

ในกรณีของเทมเพลตฟังก์ชันมาตรฐาน C ++ อนุญาตเฉพาะความเชี่ยวชาญเต็มรูปแบบเท่านั้น- ไม่รวมส่วนขยายของคอมไพเลอร์!


1
@Narek ความเชี่ยวชาญเฉพาะด้านฟังก์ชันบางส่วนไม่ได้เป็นส่วนหนึ่งของมาตรฐาน (ไม่ว่าด้วยเหตุผลใดก็ตาม) ฉันคิดว่า MSVC สนับสนุนเป็นส่วนเสริม หลังจากนั้นไม่นานคอมไพเลอร์อื่นก็จะได้รับอนุญาตเช่นกัน
iammilind

1
@iammilind: ไม่มีปัญหา ดูเหมือนเขาจะรู้อยู่แล้ว นั่นคือเหตุผลที่เขาพยายามใช้เทมเพลตฟังก์ชันเช่นกัน เลยแก้ไขใหม่ทำให้ตอนนี้ชัดเจน
Nawaz

22
ใครที่สามารถอธิบายได้ว่าทำไมไม่อนุญาตให้ใช้ความเชี่ยวชาญเฉพาะบางส่วน
HelloGoodbye

2
@NHDaly มันไม่ได้ให้ข้อผิดพลาดที่ไม่ชัดเจนเนื่องจาก 1 ฟังก์ชั่นนั้นตรงกันดีกว่าฟังก์ชั่นอื่น ๆ เพราะเหตุใดจึงเลือก(T, T)มากกว่า(T1, T2)สำหรับ(int, int)เป็นเพราะอดีตรับประกันว่ามี 2 พารามิเตอร์และทั้งสองประเภทเดียวกัน หลังรับประกันว่ามี 2 พารามิเตอร์เท่านั้น คอมไพเลอร์เลือกคำอธิบายที่ถูกต้องเสมอ เช่นถ้าคุณต้องเลือกระหว่าง 2 คำอธิบายของ "แม่น้ำ" คุณจะเลือกอันไหน? "collection of water" เทียบกับ "collection of water flow"
iammilind

1
@kfsone ฉันคิดว่าฟีเจอร์นี้อยู่ระหว่างการตรวจสอบจึงเปิดให้ตีความได้ คุณสามารถอ้างถึงส่วนopen-std นี้ซึ่งฉันเห็นในเหตุใดมาตรฐาน C ++ จึงไม่อนุญาตความเชี่ยวชาญเฉพาะบางส่วนของเทมเพลตฟังก์ชัน
iammilind

44

เนื่องจากไม่อนุญาตให้มีความเชี่ยวชาญเฉพาะบางส่วน - ดังที่คำตอบอื่น ๆ ชี้ให้เห็น - คุณสามารถแก้ไขได้โดยใช้std::is_sameและ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);
}

เอาท์พุต:

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

แก้ไข : ในกรณีที่คุณจำเป็นต้องสามารถรักษากรณีอื่น ๆ ทั้งหมดที่เหลือได้คุณสามารถเพิ่มคำจำกัดความที่ระบุว่ากรณีที่ได้รับการรักษาแล้วไม่ควรตรงกันมิฉะนั้นคุณจะตกอยู่ในคำจำกัดความที่ไม่ชัดเจน คำจำกัดความอาจเป็น:

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

ซึ่งผลิต:

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

แม้ว่าทุกกรณีจะดูน่าเบื่อไปหน่อยเนื่องจากคุณต้องบอกคอมไพเลอร์ทุกอย่างที่คุณได้ทำไปแล้ว แต่ก็ทำได้ดีมากที่จะปฏิบัติตามความเชี่ยวชาญได้ถึง 5 หรือไม่กี่อย่าง


ไม่จำเป็นต้องทำเช่นนี้จริง ๆ เนื่องจากสามารถจัดการได้โดยการทำงานมากเกินไปในรูปแบบที่ง่ายและชัดเจนกว่ามาก
Adrian

2
@ เอเดรียนฉันไม่สามารถคิดถึงวิธีการทำงานที่มากเกินไปในการแก้ปัญหานี้ได้ คุณสังเกตเห็นว่าไม่อนุญาตให้มีการโอเวอร์โหลดบางส่วนใช่ไหม? แบ่งปันวิธีแก้ปัญหาของคุณกับเราหากคุณคิดว่าชัดเจนกว่า
Rubens

1
มีวิธีอื่นในการจับฟังก์ชันเทมเพลตทั้งหมดได้อย่างง่ายดายหรือไม่?
นิค

15

ความเชี่ยวชาญพิเศษคืออะไร?

หากคุณต้องการเข้าใจเทมเพลตจริงๆคุณควรดูภาษาที่ใช้งานได้ โลกของเทมเพลตใน C ++ เป็นภาษาย่อยที่ใช้งานได้จริงของตัวเอง

ในภาษาที่ใช้งานได้การเลือกทำได้โดยใช้การจับคู่รูปแบบ :

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

ในขณะที่คุณสามารถดูเราเกินisJustความหมายของ

เทมเพลตคลาส C ++ ทำงานในลักษณะเดียวกันทุกประการ คุณระบุคำประกาศหลักซึ่งระบุจำนวนและลักษณะของพารามิเตอร์ อาจเป็นเพียงการประกาศหรือทำหน้าที่เป็นคำจำกัดความ (ทางเลือกของคุณ) จากนั้นคุณสามารถ (หากต้องการ) ให้ความเชี่ยวชาญของรูปแบบและเชื่อมโยงกับคลาสอื่น (มิฉะนั้นจะโง่) .

สำหรับฟังก์ชั่นเทมเพลตความเชี่ยวชาญจะค่อนข้างอึดอัดกว่า: มันขัดแย้งกับความละเอียดเกินพิกัด ด้วยเหตุนี้จึงมีการตัดสินใจว่าความเชี่ยวชาญพิเศษจะเกี่ยวข้องกับเวอร์ชันที่ไม่ใช่เฉพาะทางและความเชี่ยวชาญพิเศษจะไม่ได้รับการพิจารณาในระหว่างการแก้ไขปัญหาโอเวอร์โหลด ดังนั้นอัลกอริทึมสำหรับการเลือกฟังก์ชันที่ถูกต้องจะกลายเป็น:

  1. ดำเนินการแก้ปัญหาโอเวอร์โหลดระหว่างฟังก์ชันปกติและเทมเพลตที่ไม่ใช่เฉพาะ
  2. หากเลือกเทมเพลตที่ไม่ใช่แบบพิเศษให้ตรวจสอบว่ามีความเชี่ยวชาญพิเศษสำหรับเทมเพลตที่จะจับคู่ได้ดีกว่าหรือไม่

(สำหรับการรักษาเชิงลึกดูGotW # 49 )

ด้วยเหตุนี้ความเชี่ยวชาญของเทมเพลตจึงเป็นพลเมืองโซนที่สอง (ตามตัวอักษร) เท่าที่ฉันกังวลเราจะดีกว่าถ้าไม่มีพวกเขา: ฉันยังไม่พบกรณีที่ไม่สามารถแก้ไขการใช้เทมเพลตเฉพาะทางด้วยการใช้งานมากเกินไปแทน

นี่เป็นความเชี่ยวชาญพิเศษของเทมเพลตหรือไม่

ไม่มันเป็นเพียงการโอเวอร์โหลดและมันก็โอเค ในความเป็นจริงการโอเวอร์โหลดมักจะทำงานได้ตามที่เราคาดหวังในขณะที่ความเชี่ยวชาญพิเศษอาจเป็นเรื่องที่น่าประหลาดใจ (โปรดจำบทความ GotW ที่ฉันลิงก์ไว้)


"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."แล้วพารามิเตอร์เทมเพลตที่ไม่ใช่ประเภทล่ะ
Jules GM

@ Julius: คุณยังสามารถใช้การโอเวอร์โหลดได้แม้ว่าจะแนะนำพารามิเตอร์ดัมมี่เช่นboost::mpl::integral_c<unsigned, 3u>. วิธีแก้ปัญหาอื่นอาจใช้enable_if/ disable_ifแม้ว่าจะเป็นคนละเรื่องก็ตาม
Matthieu M.

8

ไม่อนุญาตให้ใช้ความเชี่ยวชาญเฉพาะบางส่วนที่ไม่ใช่คลาสและไม่ใช่ตัวแปร แต่ตามที่กล่าวไว้:

ปัญหาทั้งหมดในวิทยาการคอมพิวเตอร์สามารถแก้ไขได้ด้วยการกำหนดทิศทางอีกระดับหนึ่ง —— เดวิดวีลเลอร์

การเพิ่มคลาสเพื่อส่งต่อการเรียกใช้ฟังก์ชันสามารถแก้ปัญหานี้ได้นี่คือตัวอย่าง:

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

ไม่ตัวอย่างเช่นคุณสามารถเชี่ยวชาญตามกฎหมายได้std::swapแต่คุณไม่สามารถกำหนดโอเวอร์โหลดของคุณเองได้ตามกฎหมาย นั่นหมายความว่าคุณไม่สามารถstd::swapทำงานกับเทมเพลตคลาสที่คุณกำหนดเองได้

การใช้งานมากเกินไปและความเชี่ยวชาญเฉพาะบางส่วนอาจมีผลเช่นเดียวกันในบางกรณี แต่ยังห่างไกลจากทั้งหมด


4
นั่นเป็นเหตุผลที่คุณใส่swapเนมสเปซมากเกินไป
jpalecek

2

คำตอบที่ล่าช้า แต่ผู้อ่านบางคนอาจพบว่ามีประโยชน์: บางครั้งฟังก์ชันตัวช่วยที่ออกแบบมาเพื่อให้มีความเชี่ยวชาญสามารถแก้ปัญหาได้เช่นกัน

ลองนึกดูว่านี่คือสิ่งที่เราพยายามแก้ไข:

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

ตกลงความเชี่ยวชาญของฟังก์ชันเทมเพลตบางส่วนเราไม่สามารถทำเช่นนั้นได้ ... ดังนั้นเรามา "ส่งออก" ส่วนที่จำเป็นสำหรับความเชี่ยวชาญพิเศษไปยังฟังก์ชันตัวช่วยเชี่ยวชาญส่วนนั้นและใช้:

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

สิ่งนี้อาจน่าสนใจโดยเฉพาะอย่างยิ่งหากทางเลือกอื่น (โอเวอร์โหลดปกติแทนที่จะเป็นความเชี่ยวชาญวิธีแก้ปัญหาที่ Rubens เสนอ ... - ไม่ใช่ว่าสิ่งเหล่านี้ไม่ดีหรือของฉันดีกว่า แต่อีกอันหนึ่ง) จะแบ่งปันรหัสทั่วไปค่อนข้างมาก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.