2015-01-30 15 views
50

Hãy nói rằng tôi đang viết một hàm để in theo chiều dài của một chuỗi:Tại sao phân rã của con trỏ lại ưu tiên hơn một mẫu suy luận?

template <size_t N> 
void foo(const char (&s)[N]) { 
    std::cout << "array, size=" << N-1 << std::endl; 
} 

foo("hello") // prints array, size=5 

Bây giờ tôi muốn mở rộng để hỗ trợ foophi -arrays:

void foo(const char* s) { 
    std::cout << "raw, size=" << strlen(s) << std::endl; 
} 

Nhưng nó quay ra điều này sẽ phá vỡ mục đích sử dụng ban đầu của tôi:

foo("hello") // now prints raw, size=5 

Tại sao? Điều đó sẽ không yêu cầu một chuyển đổi mảng-to-con trỏ, trong khi các mẫu sẽ là một kết hợp chính xác? Có cách nào để đảm bảo rằng chức năng mảng của tôi được gọi?

+1

Liên quan: http://stackoverflow.com/questions/5347444/overload-resolution-and-arrays-which-function-should-be-called – 0x499602D2

+3

'foo <> (" hello ");' sẽ gọi mẫu , nếu nó giúp. Tôi nghĩ đó là cách đơn giản để từ chối không phải mẫu. Và để buộc không sử dụng mẫu '(& foo) (" hello ")' - bạn có thể lấy địa chỉ của một mẫu không phải là mẫu, nhưng không phải là mẫu. –

Trả lời

42

Lý do cơ bản cho sự không rõ ràng (tuân thủ tiêu chuẩn) dường như nằm trong chi phí chuyển đổi: Độ phân giải quá tải cố gắng giảm thiểu các hoạt động được thực hiện để chuyển đổi đối số thành thông số tương ứng. Một mảng là có hiệu quả là con trỏ đến phần tử đầu tiên của nó, được trang trí với một số thông tin kiểu thời gian biên dịch. Chuyển đổi mảng-thành-con trỏ không chi phí nhiều hơn ví dụ: lưu địa chỉ của chính mảng đó, hoặc khởi tạo một tham chiếu đến nó. Từ quan điểm đó, sự mơ hồ có vẻ hợp lý, mặc dù khái niệm nó không trực quan (và có thể là phân nhánh). Trong thực tế, đối số này áp dụng cho tất cả các biến đổi Lvalue, như được đề xuất bởi các báo giá dưới đây. Một ví dụ khác:

void g() {} 

void f(void(*)()) {} 
void f(void(&)()) {} 

int main() { 
    f(g); // Ambiguous 
} 

Sau đây là bắt buộc standardese. Các hàm không phải là chuyên môn của một số mẫu chức năng được ưu tiên hơn so với các hàm nếu cả hai đều là một kết quả tương đương tốt (xem [over.match.best]/(1.3), (1.6)). Trong trường hợp của chúng tôi, chuyển đổi được thực hiện là chuyển đổi mảng-thành-con trỏ, là một phép chuyển đổi Lvalue với xếp hạng Đối sánh chính xác (theo bảng 12 trong [over.ics.user]). [Over.ics.rank]/3:

  • chuỗi chuyển đổi tiêu chuẩn S1 là một chuỗi chuyển đổi tốt hơn so với trình tự chuyển đổi tiêu chuẩn S2 nếu

    • S1 là một dãy con đúng S2 (so sánh các chuỗi chuyển đổi theo dạng chuẩn tắc được xác định bởi 13.3.3.1.1, trừ bất kỳ chuyển đổi Lvalue nào; ered là một dãy con của bất kỳ phi sắc chuyển đổi chuỗi) hoặc, nếu không muốn nói rằng,

    • cấp bậc S1 là tốt hơn so với cấp bậc S2, hoặc S1S2 có cùng ngạch và được phân biệt bởi các quy tắc trong đoạn dưới đây, hoặc, nếu không muốn nói rằng,

    • [..]

Điểm bullet đầu tiên không bao gồm chuyển đổi của chúng tôi (vì nó là một Chuyển đổi Lvalue).Điều thứ hai yêu cầu sự khác biệt về xếp hạng, không có mặt vì cả hai chuyển đổi đều có xếp hạng đối sánh chính xác; "Quy tắc trong đoạn bên dưới", tức là trong [over.ics.rank]/4, cũng không bao gồm chuyển đổi mảng-điểm trỏ.
Vì vậy, hãy tin hay không, không có trình tự chuyển đổi nào tốt hơn cả chuỗi kia và do đó, tải lên char const* -overload.


Cách giải quyết khác: Xác định quá tải thứ hai làm mẫu chức năng, sau đó đặt một phần đá vào và chọn lần đầu tiên.

template <typename T> 
auto foo(T s) 
    -> std::enable_if_t<std::is_convertible<T, char const*>{}> 
{ 
    std::cout << "raw, size=" << std::strlen(s) << std::endl; 
} 

Demo.

+0

Hãy tưởng tượng một hàm 'bar' có một đối tượng kiểu' X'. Ngoài ra, 'X' chỉ có một hàm tạo lấy một' const char * 'làm đối số. Bạn có thể gọi 'bar (" hello ");' mặc dù điều này có vẻ như bạn đang nhận được hai chuyển đổi, mảng-to-pointer và pointer-to-X, khi bình thường chỉ là một chuyển đổi ngầm được cho phép. Tôi đoán chúng tôi nhận được phân rã mảng-to-con trỏ "miễn phí". Đó có phải là cách chấp nhận được không? –

+2

@AaronMcDaid Không. Chuyển đổi mảng thành con trỏ là một chuỗi chuyển đổi chuẩn, trong khi hàm tạo là chuỗi chuyển đổi do người dùng xác định. Sau này chỉ được phép một lần, nhưng trước đây cũng có thể xảy ra kết hợp với nó. :) – Columbo

+0

* hàm tạo chuyển đổi được gọi là một phần của chuỗi chuyển đổi do người dùng xác định. Chuỗi chuyển đổi do người dùng xác định bao gồm chuyển đổi do người dùng xác định (gọi ctor) cộng với trình tự bao phủ tiêu chuẩn ban đầu (mảng thành con trỏ) cộng với chuỗi chuyển đổi chuẩn cuối cùng (tại đây, danh tính). Tất nhiên nó không phải là trình tự. – Columbo

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