2013-07-02 27 views
6

Giả sử tôi có chức năng mẫu, assign(). Phải mất một con trỏ và một giá trị và gán giá trị cho mục tiêu của con trỏ:Sử dụng một đối số để khấu trừ tham số mẫu?

template <typename T> void assign(T *a, T b) { *a = b; } 

int main() { 
    double i; 
    assign(&i, 2); 
} 

Trong trường hợp này tôi luôn muốn T được rút ra từ những số đầu tiên, nhưng có vẻ như tôi đã không làm một công việc tốt thể hiện điều này. loại 2 ‘s là int, vì vậy:

deduce.cpp:5:5: error: no matching function for call to 'assign' 
    assign(&i, 2); 
    ^~~~~~ 
deduce.cpp:1:28: note: candidate template ignored: deduced conflicting types for parameter 'T' ('double' vs. 'int') 
template void assign(T *a, T b) { *a = b; }

Có cách nào tôi có thể tuyên bố assign() để đối số thứ hai không tham gia vào khấu trừ tham số mẫu?

+0

Vì vậy, một vấn đề ở trên không hiệu quả. Giả sử 'T' là' std :: vector'. Đối số 'b' được lấy theo giá trị, sau đó sao chép (không được di chuyển) vào' a'. Một cải tiến nhỏ có thể thay đổi việc thực hiện 'assign' thành' * a = std :: move (b) ', cho các kiểu nguyên thủy không tốn kém gì, và đối với các kiểu phức tạp có thể tiết kiệm được rất nhiều. Một cải tiến lớn sẽ là hoàn hảo về phía trước 'b'. – Yakk

+0

@Yakk Hoàn toàn đồng ý - Tôi vừa viết nó như là một ví dụ về một hàm nhận con trỏ và giá trị của cùng một loại. Trong thực tế nó chỉ mất nguyên thủy và hữu ích hơn anh chàng này :). – s4y

Trả lời

11

Sử dụng hai tham số kiểu có lẽ là lựa chọn tốt nhất, nhưng nếu bạn thực sự muốn thực hiện khấu trừ chỉ từ số đầu tiên, chỉ đơn giản là làm cho thứ hai phi deducible:

template<typename T> 
void assign(T* a, typename std::identity<T>::type b); 

Một phiên bản trước của câu trả lời này được đề xuất sử dụng tính năng bí danh mẫu được giới thiệu trong C++ 11. Nhưng bí danh mẫu vẫn là ngữ cảnh có thể suy luận. Lý do chính mà std::identitystd::remove_reference ngăn chặn việc khấu trừ là các lớp mẫu có thể chuyên môn hóa, vì vậy ngay cả khi bạn có thông số kiểu mẫu, có thể một chuyên môn khác có typedef cùng loại. Vì sự mơ hồ có thể xảy ra, việc khấu trừ không xảy ra. Nhưng các bí danh mẫu loại trừ chuyên môn hóa, và do đó việc khấu trừ vẫn xảy ra.

+0

+1. Do chỉnh sửa cho câu hỏi (mà tôi bỏ qua), điều này có vẻ là câu trả lời đúng. –

+0

Đây là một câu trả lời rất hay nhưng nó không hiệu quả với tôi. Tôi nhận được cùng một "xung đột loại" lỗi từ trình biên dịch của tôi: ideone.com/GIJbj1 – s4y

+0

Hình như 'std :: remove_reference' là tương đương (nếu một chút ít có thể đọc được), quá. – s4y

3

Chỉ là giá trị 2 được suy luận theo loại int, không khớp với thông số mẫu được trừ bởi &i. Bạn cần phải sử dụng các giá trị như là một đôi:

assign(&i, 2.0); 
+0

Tôi biết. Điều đó hoạt động nhưng nó giòn và xấu xí - nếu 'i' là một 'unsigned long' thì đối số thứ hai của tôi phải là' 2ul', nếu nó là float, nó phải là '(float) 2', v.v. , phải mất một vài đối số và nhanh chóng trở nên khó chịu. – s4y

+0

@Sidnicious Sau đó, tôi đoán bạn có thể muốn đi với câu trả lời của Andy. – 0x499602D2

2

Tại sao không chỉ cần sử dụng hai loại tham số độc lập, một cho nguồn và một cho đích?

template <typename D, typename S> void assign(D *a, S b) { *a = b; } 

int main(int argc, char* argv[]) 
{ 
    double i; 
    assign(&i, 2); 
    return 0; 
} 

Nếu không thể gán thì quá trình tạo mẫu sẽ không biên dịch.

+1

lẻ bằng cách sử dụng 'D' cho nguồn và' S' cho đích –

+0

@Ben: Thật vậy :) Chỉ cần hoán đổi chúng. – Vlad

+0

Nếu bạn định làm điều này, tại sao không chuyển tiếp 'S' một cách hoàn hảo? – Yakk

4

Vấn đề là trình biên dịch đang suy luận thông tin xung đột từ đối số đầu tiên và thứ hai. Từ đối số đầu tiên, nó khấu trừ Tdouble (i là ; từ cột thứ hai, nó khấu trừ T thành int (loại 2int).

Bạn có hai khả năng chính ở đây:

  • Luôn được rõ ràng về các loại lập luận của bạn:

    assign(&i, 2.0); 
    //   ^^^^ 
    
  • Hoặc hãy mẫu chức năng của bạn chấp nhận hai thông số mẫu:

    template <typename T, typename U> 
    void assign(T *a, U b) { *a = b; } 
    

    Trong trường hợp này, bạn có thể muốn SFINAE-chế mẫu để nó không partecipate quá tải độ phân giải trong trường hợp U không chuyển đổi được T:

    #include <type_traits> 
    
    template <typename T, typename U, 
        typename std::enable_if< 
         std::is_convertible<U, T>::value>::type* = nullptr> 
    void assign(T *a, U b) { *a = b; } 
    

    Nếu bạn không cần phải loại trừ các chức năng của bạn từ sự quá tải thiết lập khi U không chuyển đổi được T, bạn có thể muốn có một sự khẳng định tĩnh bên assign() để tạo ra một lỗi biên dịch đẹp hơn:

    #include <type_traits> 
    
    template<typename T, typename U> 
    void assign(T *a, U b) 
    { 
        static_assert(std::is_convertible<T, U>::value, 
         "Error: Source type not convertible to destination type."); 
    
        *a = b; 
    } 
    
+0

Hoặc 'static_assert' trong hàm, vì bạn không có quá tải khác có thể thực hiện được trong trường hợp không chuyển đổi được. – Praetorian

+0

@Praetorian: Hoặc, vâng. Tôi đã không nghĩ về nó bởi vì chức năng là nhỏ và thông báo lỗi sẽ là dễ hiểu anyway, nhưng bạn nói đúng, đó là một lựa chọn –

+0

Xin lỗi về việc chỉnh sửa muộn - đây là một câu trả lời khá hợp pháp. Upvoted, ít nhất. – s4y

0

Hoặc, bạn có thể sử dụng decltype để định kiểu đối số thứ hai là loại đầu tiên.

template <typename T> void assign(T *a, T b) { *a = b; } 

int main() { 
    double i; 
    assign(&i, (decltype(i))2); 
} 
+0

Vâng, nhưng bạn phải nhớ làm điều đó mỗi khi bạn gọi nó. Nó trở nên cực kỳ nhanh - Tôi đã đăng câu hỏi này bởi vì tôi đã cố gắng cải thiện một chức năng lấy vài con số làm đối số. – s4y

1

nỗ lực của tôi sẽ giống như thế này:

template<typename T, typename U> 
typename std::enable_if< std::is_convertible< U&&, T >::value >::type // not quite perfect 
assign(T* dest, U&& src) { 
    *dest = std::forward<U>(src); 
} 

đối số thứ hai là bất cứ điều gì bạn có thể chuyển đổi sang một T, nhưng chúng tôi mang nó bằng cách tham chiếu phổ thông và có điều kiện di chuyển nó vào *dest. Tôi kiểm tra khả năng chuyển đổi trong chữ ký thay vì để cơ thể không biên dịch được, bởi vì thất bại-để-tìm-một-quá tải có vẻ lịch sự hơn là không biên dịch cơ thể.

Live example.

So với các đơn giản:

template<typename T> 
void assign(T* dest, typename std::identity<T>::type src) { 
    *dest = std::move(src); 
} 

trên tiết kiệm 1 move.Nếu bạn có một lớp học đắt tiền để di chuyển, hoặc một lớp học chỉ sao chép và tốn kém để sao chép, điều này có thể tiết kiệm một số lượng đáng kể.

0

Rõ ràng std::identity không còn ở đó nữa (Is there a reason why there is not std::identity in the standard library?)

Nhưng bạn có thể xác định loại tham số trong danh sách loại tham số, khi gọi hàm:

template <typename T> void assign(T *a, T b) { *a = b; } 

int main() { 
    double i; 
    assign<double>(&i, 2); 
} 

Bằng cách này, trình biên dịch sẽ chuyển đổi đối số đầu vào số nguyên để tăng gấp đôi để khớp với mẫu hàm, không có trích đối số.

Live demo

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