2009-10-02 39 views
20

Vì vậy, tôi biết rằng có một sự khác biệt giữa hai mẩu các mã:Sự khác nhau giữa chuyên môn mẫu và quá tải cho các chức năng?

template <typename T> 
T inc(const T& t) 
{ 
    return t + 1; 
} 

template <> 
int inc(const int& t) 
{ 
    return t + 1; 
} 

template <typename T> 
T inc(const T& t) 
{ 
    return t + 1; 
} 

int inc(const int& t) 
{ 
    return t + 1; 
} 

Tôi đang bối rối như những gì khác biệt về chức năng giữa hai đều. Ai đó có thể hiển thị một số tình huống trong đó những đoạn mã này hoạt động khác nhau không?

+0

Tôi đã bỏ phiếu tán thành câu hỏi vì đây là một trong những vùng tối trong C++ (ít nhất là đối với tôi)! – AraK

+5

Bạn đặc biệt yêu cầu điều gì? Chỉ cần một số khác biệt, hoặc một số trường hợp nguy hiểm được đặt ra? Ví dụ, chắc chắn 'inc <> (10);' ứng xử khác nhau cho hai đoạn trích này - nhưng đó là những gì bạn muốn nghe? –

Trả lời

14

Tôi chỉ có thể nghĩ đến một vài khác biệt - dưới đây là một số ví dụ không nhất thiết gây hại (tôi nghĩ). Tôi đang bỏ qua các định nghĩa để giữ cho nó ngắn gọn

template <typename T> T inc(const T& t); 
namespace G { using ::inc; } 
template <> int inc(const int& t); 
namespace G { void f() { G::inc(10); } } // uses explicit specialization 

// --- against --- 

template <typename T> T inc(const T& t); 
namespace G { using ::inc; } 
int inc(const int& t); 
namespace G { void f() { G::inc(10); } } // uses template 

Đó là vì không tìm thấy chuyên môn bằng tra cứu tên, do đó việc khai báo sử dụng sẽ tự động xem xét chuyên môn sau được giới thiệu.

Sau đó, tất nhiên, bạn không thể chuyên môn hóa một phần mẫu chức năng.Quá tải tuy nhiên hoàn thành một cái gì đó rất giống nhau bởi đặt hàng từng phần (sử dụng các loại khác nhau bây giờ, để làm cho quan điểm của tôi)

template <typename T> void f(T t); // called for non-pointers 
template <typename T> void f(T *t); // called for pointers. 

int a; 
void e() { 
    f(a); // calls the non-pointer version 
    f(&a); // calls the pointer version 
} 

Điều đó sẽ không thể thực hiện với chuyên môn hóa rõ ràng chức năng template. Một ví dụ khác là khi tài liệu tham khảo có liên quan, trong đó nguyên nhân khấu trừ mẫu tranh luận để tìm kiếm một kết hợp chính xác của các loại liên quan (cơ sở modulo/nguồn gốc các mối quan hệ giai cấp và constness):

template<typename T> void f(T const &); 
template<> void f(int * const &); 

template<typename T> void g(T const &); 
void g(int * const &); 

int a[5]; 
void e() { 
    // calls the primary template, not the explicit specialization 
    // because `T` is `int[5]`, not `int *` 
    f(a); 

    // calls the function, not the template, because the function is an 
    // exact match too (pointer conversion isn't costly enough), and it's 
    // preferred. 
    g(a); 
} 

tôi khuyên bạn nên luôn luôn sử dụng quá tải, bởi vì nó phong phú hơn (cho phép một cái gì đó như chuyên môn hóa một phần sẽ cho phép), và ngoài ra bạn có thể đặt chức năng trong bất kỳ không gian tên bạn muốn (mặc dù sau đó nó không quá tải nghiêm trọng nữa). Ví dụ, thay vì phải chuyên std::swap trong không gian tên std::, bạn có thể đặt quá tải swap trong không gian tên của riêng bạn và làm cho nó có thể gọi bằng ADL.

Bất kể bạn làm gì, không bao giờ kết hợp chuyên môn và quá tải, nó sẽ là một đống lộn xộn như this article chỉ ra. Tiêu chuẩn có một đoạn văn đáng yêu về nó

Vị trí khai báo chuyên môn rõ ràng cho mẫu chức năng, thành phần dữ liệu tĩnh của mẫu lớp, lớp thành viên của mẫu lớp, mẫu lớp thành viên của các mẫu lớp, các hàm thành viên của các mẫu lớp, các hàm thành viên của các mẫu thành viên của các mẫu lớp, các hàm thành viên của các mẫu thành viên của các lớp không mẫu, các mẫu hàm thành viên của các lớp thành viên của các mẫu lớp, v.v. của các mẫu lớp, các mẫu lớp thành viên của các lớp không phải mẫu, các mẫu lớp thành viên của các mẫu lớp, v.v., có thể ảnh hưởng đến việc một chương trình có được định dạng tốt hay không. arations và điểm của họ về instantiation trong đơn vị dịch thuật như quy định ở trên và dưới đây. Khi viết chuyên môn, hãy cẩn thận về vị trí của nó; hoặc để làm cho nó biên dịch sẽ là một thử nghiệm như để kindle tự thiêu của nó.

+0

Bài viết hay. . – rlbond

+0

+1 tốt - Tôi thích :) –

+0

cảm ơn folks :) –

5

Chuyên môn về mẫu chung chung hơn là quá tải. Bạn có thể chuyên những thứ như lớp học chứ không chỉ là các hàm đơn giản. Quá tải chỉ áp dụng cho các chức năng.

CẬP NHẬT: Để làm rõ thêm về nhận xét của AraK, bạn thực sự so sánh táo và cam ở đây. Chức năng quá tải được sử dụng để giới thiệu khả năng có các chức năng khác nhau chia sẻ một tên duy nhất, nếu chúng có chữ ký khác nhau. Chuyên môn về mẫu được sử dụng để xác định đoạn mã cụ thể cho thông số loại cụ thể. Bạn không thể có chuyên môn về mẫu nếu bạn không có mẫu. Nếu bạn loại bỏ đoạn mã đầu tiên khai báo mẫu chung, bạn sẽ nhận được một lỗi thời gian biên dịch nếu bạn cố gắng sử dụng chuyên môn mẫu.

Vì vậy, mục tiêu của chuyên môn mẫu khá khác với quá tải hàm. Chúng chỉ xảy ra tương tự như trong ví dụ của bạn trong khi chúng khác nhau về cơ bản.

Nếu bạn cung cấp quá tải, bạn khai báo một phương thức độc lập có cùng tên. Bạn không ngăn mẫu được sử dụng với thông số loại cụ thể. Để chứng minh thực tế này, hãy thử:

template <typename T> 
T inc(const T& t) 
{ 
    return t + 1; 
} 

int inc(const int& t) 
{ 
    return t + 42; 
} 

#include <iostream> 
int main() { 
    int x = 0; 
    x = inc<int>(x); 
    std::cout << "Template: " << x << std::endl; // prints 1. 

    x = 0; 
    x = inc(x); 
    std::cout << "Overload: " << x << std::endl; // prints 42. 
} 

Như bạn có thể thấy, trong ví dụ này, có hai khác biệt inc chức năng cho int giá trị: inc(const int&)inc<int>(const int&). Bạn không thể mở rộng mẫu chung bằng cách sử dụng int nếu bạn đã sử dụng chuyên môn mẫu.

+2

Lưu ý tốt, mặc dù nó không trả lời câu hỏi. – AraK

+1

AraK: Câu hỏi, như tôi hiểu, về cơ bản, tại sao có hai cách để đạt được điều tương tự. Nó cho thấy một kết quả tương tự đạt được bằng hai phương pháp và yêu cầu sự khác biệt giữa hai phương pháp. Câu trả lời của tôi là hai phương pháp này áp dụng cho các bối cảnh khác nhau và một ví dụ duy nhất trong đó hai phương pháp áp dụng không làm cho chúng tương đương nhau. –

+0

Tôi đã nói đó là một lưu ý tốt, nhưng không trả lời sự khác biệt về chức năng giữa * chức năng chuyên môn về mẫu * và * quá tải chức năng *. Bạn đang nói về chuyên môn mẫu nói chung. Tôi nghĩ rằng tiêu đề là sai lầm chút ít. – AraK

1

AFAIK không có sự khác biệt về chức năng. Tất cả những gì tôi có thể thêm là nếu bạn có cả chuyên môn về chức năng mẫu và hàm bình thường được xác định thì không có sự mơ hồ quá tải vì chức năng thông thường được ưu tiên.

4

Một ví dụ:

#include <cstdio> 

template <class T> 
void foo(T) 
{ 
    puts("T"); 
} 

//template <> 
void foo(int*) 
{ 
    puts("int*"); 
} 

template <class T> 
void foo(T*) 
{ 
    puts("T*"); 
} 

int main() 
{ 
    int* a; 
    foo(a); 
} 

Nó thực sự là gợi ý mà bạn sử dụng không mẫu quá tải cho các chức năng và để chuyên môn hóa cho các lớp học. Nó được thảo luận ở độ dài lớn hơn trong Why Not Specialize Function Templates?

1

Chỉ để xây dựng trên điểm đầu tiên được đề cập bởi litb trong answer của mình. Các chuyên ngành chỉ được kiểm tra khi độ phân giải quá tải thực sự đã chọn một mẫu chính. Kết quả có thể dẫn đến một số bất ngờ nơi một chức năng bị quá tải và có chuyên môn rõ ràng:

template <typename T> void foo (T); // Primary #1 
template <> void foo<int*> (int*); // Specialization of #1 

template <typename T> void foo (T*); // Primary #2 

void bar (int * i) 
{ 
    foo(i); 
} 

Khi chọn có chức năng gọi điện thoại, các bước sau đây xảy ra:

  1. Tên tra cứu tìm thấy cả hai mẫu chính.
  2. Mỗi mẫu là nỗ lực phân giải chuyên ngành và quá tải để chọn chức năng tốt nhất dựa trên chuyển đổi giữa các đối số và tham số.
  3. Trong trường hợp này, không có sự khác biệt về chất lượng của các chuyển đổi.
  4. Quy tắc đặt hàng một phần sau đó được sử dụng để chọn mẫu chuyên biệt nhất. Trong trường hợp này là chữ cái thứ hai "foo (T *)".

Chỉ sau khi các bước này, khi các chức năng tốt nhất đã được lựa chọn sẽ chuyên môn rõ ràng của hàm chọn được xem xét. (Trong trường hợp này, trường hợp số 2 không có gì nên không được xem xét).

Cách duy nhất để gọi điện cho chuyên môn hóa rõ ràng trên đây, là để thực sự sử dụng đối số mẫu rõ ràng trong cuộc gọi:

void bar (int * i) 
{ 
    foo<int*> (i); // expliit template argument forces use of primary #1 
} 

Một nguyên tắc nhỏ là cố gắng để tránh việc quá tải mà còn là explicily chuyên ngành , vì các quy tắc kết quả khá phức tạp.

Các vấn đề liên quan