58

Tôi biết rằng mã dưới đây là một đặc tả từng phần của một lớp:Chức năng chuyên môn hóa từng phần của C++?

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


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

Ngoài ra tôi biết rằng C++ không cho phép hàm mẫu chuyên môn hóa từng phần (chỉ toàn được phép). Nhưng mã của tôi có nghĩa là tôi đã từng chuyên môn hóa một phần mẫu chức năng của tôi cho một đối số kiểu giống nhau không? Bởi vì nó hoạt động cho Microsoft Visual Studio 2010 Express! Nếu không, thì bạn có thể giải thích khái niệm chuyên môn hóa một phần không?

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

Look cho rằng tương tự của chuyên môn hóa lớp. Nếu nó được gọi là chuyên môn lớp, thì tại sao tôi nên xem xét cùng một điều cho chức năng như quá tải ?? – Narek

+0

Không, cú pháp chuyên môn là khác nhau. Nhìn vào cú pháp chuyên môn hóa chức năng (nghĩa vụ) trong câu trả lời của tôi dưới đây. – iammilind

+1

Tại sao điều này không gây ra lỗi "Call to max is ambigious"? Làm thế nào 'max (5,5)' giải quyết thành 'max (T const &, T const &) [với T = int]' và không 'max (T1 const &, T2 const &) [với T1 = int và T2 = int]' ? – NHDaly

Trả lời

58

Trong ví dụ này, bạn thực sự là quá tải (không chuyên) chức năng max<T1,T2>. chuyên môn hóa một phần cú pháp nên đã xem xét hơi như dưới đây (mà nếu nó được phép):

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

[Lưu ý: trong trường hợp của một hàm mẫu, chỉ đầy đủ chuyên môn được phép bởi tiêu chuẩn C++ (không bao gồm phần mở rộng của trình biên dịch).]

+0

Nó không cho phép bởi vì nó có thể gây nhầm lẫn cho trình biên dịch để gây nhầm lẫn với phiên bản chuyên biệt của chức năng, phải không? Nếu nó là đúng, những gì sẽ gây nhầm lẫn cho trình biên dịch nếu chức năng không bị quá tải theo cách tôi đã viết nhưng nó đã được chuyên môn theo cách bạn đã viết (theo một cách không được phép)? – Narek

+1

@Narek, Chuyên môn hóa một phần chức năng không phải là một phần của tiêu chuẩn (vì bất kỳ lý do nào). Tôi nghĩ rằng MSVC hỗ trợ nó như là một phần mở rộng. Có thể sau một thời gian, nó cũng sẽ được cho phép bởi các trình biên dịch khác. – iammilind

+0

@iammilind: Đã chỉnh sửa bài đăng và +1. – Nawaz

4

No. Ví dụ, bạn có thể định nghĩa hợp pháp std::swap, nhưng bạn không thể xác định chính xác quá tải của chính mình. Điều đó có nghĩa là bạn không thể làm cho std::swap hoạt động cho mẫu lớp tùy chỉnh của riêng bạn.

Quá tải và một phần chuyên môn hóa có thể có tác dụng tương tự trong một số trường hợp, nhưng cách xa tất cả.

+4

Đó là lý do tại sao bạn đặt quá tải 'swap' trong không gian tên của mình. – jpalecek

12

Chuyên môn hóa là gì?

Nếu bạn thực sự muốn hiểu các mẫu, bạn nên xem các ngôn ngữ chức năng. Thế giới của các mẫu trong C++ là một ngôn ngữ phụ hoàn toàn chức năng của riêng nó.

Trong các ngôn ngữ chức năng, lựa chọn được thực hiện bằng Pattern Matching:

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

Như bạn có thể thấy, chúng tôi quá tải định nghĩa của isJust.

Vâng, các mẫu lớp C++ hoạt động chính xác theo cùng một cách. Bạn cung cấp một tuyên bố chính , tuyên bố số lượng và tính chất của các thông số. Nó có thể chỉ là một tuyên bố, hoặc cũng hoạt động như một định nghĩa (lựa chọn của bạn), và sau đó bạn có thể (nếu bạn muốn) cung cấp các chuyên môn của mẫu và liên kết với chúng một phiên bản khác (nếu không nó sẽ là ngớ ngẩn) của lớp .

Đối với các chức năng mẫu, chuyên môn có phần khó xử hơn: nó xung đột phần nào với độ phân giải quá tải. Như vậy, nó đã được quyết định rằng một chuyên môn sẽ liên quan đến một phiên bản không chuyên biệt, và các chuyên ngành sẽ không được xem xét trong quá trình giải quyết quá tải.Do đó, các thuật toán để lựa chọn các chức năng ngay trở thành:

  1. Thực hiện Nghị quyết tình trạng quá tải, trong số những chức năng thường xuyên và các mẫu không chuyên
  2. Nếu một mẫu không chuyên được chọn, kiểm tra xem một chuyên môn tồn tại cho nó mà có thể là một trận đấu tốt hơn

(cho vào sâu điều trị, thấy GotW #49)

như vậy, mẫu chuyên môn của chức năng là công dân thứ hai khu vực (theo nghĩa đen). Theo như tôi lo ngại, chúng tôi sẽ tốt hơn nếu không có chúng: Tôi chưa gặp phải trường hợp sử dụng chuyên môn mẫu không thể được giải quyết với quá tải thay thế.

Đây có phải là chuyên môn về mẫu không?

Không, nó đơn giản là quá tải và điều này là tốt. Trong thực tế, tình trạng quá tải thường hoạt động như chúng tôi mong đợi, trong khi các chuyên môn có thể gây ngạc nhiên (hãy nhớ bài viết tôi đã liên kết với GotW).

+0

'" Như vậy, chuyên môn hóa các chức năng mẫu là công dân vùng thứ hai (theo nghĩa đen) .Tuy nhiên tôi lo ngại, chúng tôi sẽ tốt hơn nếu không có chúng: Tôi chưa gặp phải trường hợp sử dụng chuyên môn mẫu được giải quyết với quá tải thay thế. "' Làm thế nào về với các tham số mẫu không kiểu? – Julius

+0

@Julius: bạn vẫn có thể sử dụng quá tải, mặc dù bằng cách giới thiệu một tham số giả như 'boost :: mpl :: integral_c '. Một giải pháp khác cũng có thể là sử dụng 'enable_if' /' disable_if', mặc dù đó là một câu chuyện khác. –

28

Kể từ chuyên môn hóa từng phần không được phép - như câu trả lời khác chỉ -, bạn có thể làm việc xung quanh nó bằng cách sử std::is_samestd::enable_if, như sau:

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

Output:

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

Chỉnh sửa: Trong trường hợp bạn cần để có thể xử lý tất cả các trường hợp còn lại, bạn có thể thêm định nghĩa có trạng thái các trường hợp đã được xử lý không được khớp với - nếu không bạn sẽ rơi vào các định nghĩa không rõ ràng. Định nghĩa có thể là:

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

nào sản xuất:

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

Mặc dù đây mọi trường hợp điều trông hơi nhàm chán, vì bạn phải nói với tất cả mọi thứ biên dịch bạn đã thực hiện, đó là khá khả thi để xử lý lên đến 5 hoặc một vài chuyên môn hơn.

+0

Có thực sự không cần phải làm điều này vì điều này có thể được xử lý bởi quá tải chức năng theo một cách đơn giản hơn và rõ ràng hơn nhiều. – Adrian

+2

@Adrian Tôi thực sự không thể nghĩ ra bất kỳ cách tiếp cận quá tải hàm nào khác để giải quyết vấn đề này. Bạn nhận thấy một phần quá tải không được phép, phải không? Chia sẻ với chúng tôi giải pháp của bạn, nếu bạn nghĩ rằng nó là rõ ràng hơn. – Rubens

+1

là có cách nào khác để làm ** dễ dàng ** bắt tất cả các chức năng templated? – Nick

0

Câu trả lời trễ, nhưng một số người đọc trễ có thể thấy hữu ích: Đôi khi, một hàm trợ giúp – được thiết kế sao cho nó có thể chuyên biệt – cũng có thể giải quyết vấn đề.

Vì vậy, hãy tưởng tượng, đây là những gì chúng tôi thử để giải quyết:

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

OK, một phần mẫu chức năng chuyên môn, chúng ta không thể làm điều đó ... Vì vậy, hãy "xuất khẩu" phần cần thiết cho chuyên môn hóa vào một hàm helper, chuyên rằng một và sử dụng nó:

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

này có thể rất thú vị đặc biệt là nếu các lựa chọn thay thế (quá tải bình thường thay vì chuyên ngành, các workaround được Rubens đề xuất, ...– không phải là những điều xấu hoặc của tôi tốt hơn, chỉ cần một số khác) sẽ chia sẻ khá nhiều mã phổ biến.

2

Non-class, không biến chuyên môn hóa từng phần không được phép, nhưng như đã nói:

Tất cả các vấn đề trong máy tính khoa học có thể được giải quyết bằng cách khác mức gián tiếp. - David Wheeler

Thêm một lớp học để chuyển tiếp cuộc gọi chức năng có thể giải quyết việc này, đây là một ví dụ:

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, ""); 
+0

Thanh lịch! Đây phải là câu trả lời được chấp nhận IMHO. – ulatekh