2016-09-19 18 views
11

Hãy tưởng tượng chúng ta có tình huống sau đây:Tại sao std :: copy_if chữ ký không hạn chế các loại vị

struct A 
{ 
    int i; 
}; 

struct B 
{ 
    A a; 
    int other_things; 
}; 

bool predicate(const A& a) 
{ 
    return a.i > 123; 
} 

bool predicate(const B& b) 
{ 
    return predicate(b.a); 
} 

int main() 
{ 
    std::vector<A> a_source; 
    std::vector<B> b_source; 

    std::vector<A> a_target; 
    std::vector<B> b_target; 

    std::copy_if(a_source.begin(), a_source.end(), std::back_inserter(a_target), predicate); 
    std::copy_if(b_source.begin(), b_source.end(), std::back_inserter(b_target), predicate); 

    return 0; 
} 

Cả hai cuộc gọi đến std::copy_if tạo ra một lỗi biên dịch, vì sự quá tải đúng predicate() chức năng không thể được infered bởi trình biên dịch kể từ khi std::copy_if mẫu chữ ký chấp nhận bất kỳ loại ngữ:

template<typename _IIter, 
     typename _OIter, 
     typename _Predicate> 
_OIter copy_if(// etc... 

tôi thấy độ phân giải quá tải làm việc nếu tôi quấn std::copy_if cuộc gọi vào một constra hơn chức năng mẫu ined:

template<typename _IIter, 
     typename _OIter, 
     typename _Predicate = bool(const typename std::iterator_traits<_IIter>::value_type&) > 
void copy_if(_IIter source_begin, 
       _IIter source_end, 
       _OIter target, 
       _Predicate pred) 
{ 
    std::copy_if(source_begin, source_end, target, pred); 
} 

Câu hỏi của tôi là: tại sao STL không bị hạn chế như thế này? Từ những gì tôi đã thấy, nếu loại _Predicate không phải là một hàm trả về bool và chấp nhận loại đầu vào được lặp lại, nó sẽ tạo ra lỗi trình biên dịch. Vậy tại sao không đặt giới hạn này trong chữ ký, để độ phân giải quá tải có thể hoạt động?

+1

hạn chế của bạn quá mạnh ('const' không bắt buộc, một số chuyển đổi được phép (' int' cho 'bool')). 'decltype' sẽ cho phép yêu cầu đúng (hoặc Khái niệm), nhưng phương thức đó đã được thực hiện trước C++ 11. – Jarod42

Trả lời

12

Vì biến vị ngữ không phải là hàm, nhưng nó cũng có thể là hàm con. Và hạn chế loại functor là gần như không thể vì nó có thể là bất cứ điều gì ở tất cả miễn là nó có operator() xác định.

Thật sự tôi đề nghị bạn chuyển đổi các chức năng quá tải để một functor đa hình ở đây:

struct predicate { 
    bool operator()(const A& a) const 
    { 
     return a.i > 123; 
    } 

    bool operator()(const B& b) const 
    { 
     return operator()(b.a); 
    } 
} 

và gọi functor với một thể hiện, tức là

std::copy_if(a_source.begin(), a_source.end(), std::back_inserter(a_target), predicate()); 
std::copy_if(b_source.begin(), b_source.end(), std::back_inserter(b_target), predicate()); 
//                      ^^ here, see the() 

Sau đó, tình trạng quá tải đúng sẽ được chọn bên trong thuật toán.

+0

Điều này rất thú vị. Hai câu hỏi: (1) tại sao trong trường hợp này quá tải chính xác được chọn? (2) có thể làm cho toán tử 'operator()' tĩnh không? – nyarlathotep108

+2

@ nyarlathotep108, quảng cáo (1), mẫu bây giờ nhận cả quá tải trong gói và lựa chọn xảy ra sâu bên trong mẫu nơi có đủ thông tin để thực hiện. Quảng cáo (2), có, nó nên được, nhưng nó không tạo ra sự khác biệt nhiều như bạn cần phải tạo ra các giả dụ anyway. –

+0

Và, quả thực, lambdas được định nghĩa theo các đối tượng hàm và chỉ lambdas không bắt được chuyển đổi thành con trỏ hàm được định nghĩa (bạn phải lưu trữ các giá trị đã capture ở đâu đó!) – JohannesD

4

Sự cố này không chỉ ảnh hưởng đến các biến vị ngữ đối với thuật toán. Nó xảy ra ở bất kỳ nơi nào việc loại trừ kiểu mẫu suy ra một hàm bị quá tải. Khấu trừ loại mẫu xảy ra trước khi độ phân giải quá tải, do đó trình biên dịch thiếu thông tin theo ngữ cảnh để giải quyết sự mơ hồ.

Ràng buộc chính xác bằng văn bản sẽ phức tạp, vì nó cần đưa vào đối số tài khoản và chuyển đổi loại trả về, liên kết, lambdas, functors, mem_fn và cứ tiếp tục như vậy.

Cách dễ dàng để giải quyết sự mơ hồ (IMHO) là gọi vị từ thông qua một lambda.

std::copy_if(a_source.begin(), a_source.end(), 
     std::back_inserter(a_target), 
     [](auto&& x){ return predicate(std::forward<decltype(x)>(x)); }); 

Điều này làm giảm độ phân giải quá tải cho đến sau khi loại trừ mẫu.

gì nếu tôi từ chối (hoặc ông chủ của tôi từ chối) để nâng cấp lên C++ 14

Sau đó, tay cuộn lambda giống nhau:

struct predicate_caller 
{ 
    template<class T> 
    decltype(auto) operator()(T&& t) const 
    { 
    return predicate(std::forward<T>(t)); 
    } 
}; 

và gọi như vậy:

std::copy_if(b_source.begin(), b_source.end(), 
      std::back_inserter(b_target), 
      predicate_caller()); 
+0

Điều này chỉ hoạt động với C++ 14 lambdas chung. Đó là đủ mới mà nó có thể không có sẵn tại trình biên dịch địa phương của bạn được nêu ra. –

+0

@JanHudec Tôi không thể nghĩ đến trình biên dịch C++ 11 mà không nâng cấp không đau lên C++ 14. Quan điểm của tôi là ở lại với C++ 11 chính nó là một mô hình chống. Tuy nhiên, một đối tượng hàm với toán tử gọi là templated là đủ. –

+0

@JanHudec functor được cán bằng tay được cung cấp cho tất cả các khuôn cứng. –

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