Chuyên môn hóa từng phần mẫu hàm C ++?


87

Tôi biết rằng mã dưới đây là một phần chuyên môn hóa của một lớp:

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 chuyên môn hóa từng phần của mẫu hàm (chỉ cho phép đầy đủ). Nhưng mã của tôi có nghĩa là tôi đã chuyên biệt hóa một phần mẫu hàm của mình cho một / cùng loại đối số không? Bởi vì nó hoạt động cho Microsoft Visual Studio 2010 Express! Nếu không, bạn có thể vui lòng giải thích khái niệm chuyên môn hóa từng phần được 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;
}

Hãy tìm sự tương tự của chuyên môn hóa lớp học. Nếu nó được gọi là chuyên môn hóa lớp, thì tại sao tôi phải coi điều tương tự đối với hàm là quá tải ??
Narek

1
Không, cú pháp chuyên môn hóa là khác nhau. Hãy xem cú pháp chuyên môn hóa hàm (được cho là) ​​trong câu trả lời của tôi bên dưới.
iammilind

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

Câu trả lời:


81

Theo tiêu chuẩn chưa cho phép chuyên môn hóa từng phần chức năng. Trong ví dụ này, bạn đang thực sự quá tải & không chuyên các max<T1,T2>chức năng.
cú pháp nên đã xem xét phần nào như dưới đây, mà nếu nó được cho 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)
{                  ^^^^^ <--- [supposed] specializing here
  return 10;
}

Trong trường hợp của một mẫu hàm, tiêu chuẩn C ++ chỉ cho phép chuyên môn hóa đầy đủ , - không bao gồm các phần mở rộng của trình biên dịch!


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 gì). Tôi nghĩ MSVC hỗ trợ nó như một phần mở rộng. Có thể sau một thời gian, nó cũng sẽ được các trình biên dịch khác cho phép.
iammilind

1
@iammilind: Không sao. Anh ấy dường như đã biết điều đó. Đó là lý do tại sao anh ấy cũng đang thử điều đó cho mẫu hàm. Vì vậy, tôi đã chỉnh sửa nó một lần nữa, làm cho nó rõ ràng bây giờ.
Nawaz

20
Bất cứ ai có thể giải thích tại sao chuyên môn hóa một phần không được phép?
HelloGoodbye

2
@NHDaly, Nó không đưa ra lỗi không rõ ràng vì 1 hàm phù hợp hơn hàm còn lại. Tại sao nó chọn (T, T)over (T1, T2)for (int, int), là bởi vì cái trước đảm bảo rằng có 2 tham số và cả hai loại đều giống nhau; cái sau chỉ đảm bảo rằng có 2 tham số. Trình biên dịch luôn chọn một mô tả chính xác. ví dụ: Nếu bạn phải lựa chọn giữa 2 mô tả về "Dòng sông" bạn sẽ chọn mô tả nào? "bộ sưu tập của nước" so với "bộ sưu tập của Nước chảy".
iammilind

1
@kfsone, tôi nghĩ tính năng này đang được xem xét, do đó có thể mở để giải thích. Bạn có thể tham khảo phần open-std này , mà tôi đã thấy trong Tại sao tiêu chuẩn C ++ không cho phép chuyên môn hóa từng phần của mẫu hàm?
iammilind

44

Vì không cho phép chuyên môn hóa một phần - như các câu trả lời khác đã chỉ ra -, bạn có thể giải quyết vấn đề đó bằng cách sử dụng std::is_samestd::enable_ifnhư bên dưới:

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

Đầu ra:

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

Chỉnh sửa : Trong trường hợp bạn cần có khả năng xử lý tất cả các trường hợp còn lại, bạn có thể thêm định nghĩa cho biết các trường hợp đã được xử lý sẽ không khớp - nếu không bạn sẽ rơi vào các định nghĩa mơ hồ. Đị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");
}

Sản xuất:

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

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


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ằng cách tính quá tải chức năng theo cách đơn giả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 không được phép quá tải một phần, 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ó rõ ràng hơn.
Rubens

1
có cách nào khác để làm dễ dàng bắt tất cả các chức năng mẫu không?
Nick

15

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ữ con thuần túy chức năng của riêng nó.

Trong các ngôn ngữ chức năng, các lựa chọn được thực hiện bằng cách sử dụng Khớp mẫu :

-- 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ủaisJust .

Chà, các mẫu lớp C ++ hoạt động theo cùng một cách. Bạn cung cấp một khai báo chính , cho biết số lượng và bản chất của các tham số. Nó có thể chỉ là một khai báo, hoặc cũng đóng vai trò như một định nghĩa (sự 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 sẽ thật ngớ ngẩn) của lớp .

Đối với các hàm mẫu, việc chuyên môn hóa có phần khó xử hơn: nó xung đột phần nào với việc giải quyết quá tải. Do đó, người ta đã quyết định rằng một chuyên ngành sẽ liên quan đến một phiên bản không chuyên 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 đó, thuật toán chọn chức năng phù hợp trở thành:

  1. Thực hiện giải quyết quá tải, giữa các chức năng thông thường và các mẫu không chuyên biệt
  2. Nếu một mẫu không chuyên biệt được chọn, hãy kiểm tra xem có một chuyên ngành nào phù hợp hơn với nó hay không

(để điều trị chuyên sâu, hãy xem GotW # 49 )

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

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

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


"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."Làm thế nào về với các thông số mẫu không phải loại?
Jules GM

@Julius: bạn vẫn có thể sử dụng tính năng nạp chồng, mặc dù bằng cách giới thiệu một tham số giả chẳng hạn như boost::mpl::integral_c<unsigned, 3u>. 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.
Matthieu M.

7

Không cho phép chuyên môn hóa từng phần không lớp, không thay đổi, nhưng như đã nói:

Tất cả các vấn đề trong khoa học máy tính có thể được giải quyết bằng một cấp độ khác của hướng dẫn. —— David Wheeler

Thêm một lớp để chuyển tiếp lời gọi hàm có thể giải quyết điều 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, "");

4

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

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


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

2

Câu trả lời muộn, nhưng một số người đọc muộn có thể thấy hữu ích: Đôi khi, một chức năng trợ giúp - được thiết kế để 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 đã cố gắng 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, chuyên môn hóa một phần chức năng mẫu, chúng tôi không thể làm điều đó ... Vì vậy, hãy "xuất" phần cần thiết để chuyên môn hóa thành một hàm trợ giúp, chuyên môn hóa phần đó 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?
}

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.