2015-06-29 17 views
5
#include <functional> 
#include <iostream> 
#include <string> 
#include <vector> 

using namespace std; 

class A 
{ 
    public: 
    void doStuff(function<void (const string *)> func) const 
    { 
     cout << "Const method called" << endl; 
     for(const auto& i_string : m_vec) 
      func(i_string); 
    } 

    void doStuff(function<void (string *)> func) 
    { 
     cout << "Non-const method called" << endl; 
     doStuff([&func](const string *str) 
     { 
      auto mutableString = const_cast<string *>(str); 
      func(mutableString); 
     }); 
    } 

private: 
    vector<string *> m_vec; 
}; 

int main() 
{ 
    auto a = A{}; 

    a.doStuff([](string *str){ 
     *str = "I modified this string"; 
    }); 
} 

Trong ví dụ này, phương pháp const không bao giờ được gọi. Nếu mã có vẻ lạ, đây là những gì tôi đang cố gắng làm:Quá tải hàm với std :: đối số hàm: tại sao phương thức const không bao giờ được gọi?

Thay vì phương thức getter, tôi cho phép khách hàng lặp lại các đối tượng bằng cách truyền hàm. Để kích hoạt cả truy cập const và non-const, tôi muốn cung cấp quá tải const và không const. Hơn nữa, để tránh sao chép & dán, tôi muốn triển khai phương pháp không const theo phương pháp const: phương pháp const trong mã của tôi thực sự phức tạp hơn so với phương pháp tôi sử dụng ở đây.

Bây giờ, câu hỏi của tôi là: Nếu bạn chạy mã này, nó sẽ đệ quy gọi hàm không const cho đến khi chồng tràn. Tôi không hiểu tại sao dòng doStuff([&func](const string *str) trong phương thức không phải const gọi chính nó chứ không phải phương thức const.

Trả lời

1

Quá tải const chỉ được gọi khi phiên bản của lớp Aconst. Đây là trường hợp vì const ở phần cuối của tờ khai:

void doStuff(function<void (const string *)> func) const //This const at the end causes 
                 //The member function to only be 
                 //called when `a` is const 

Các const-vòng loại cuối cùng áp dụng cho các đối tượng this, không để các thông số. Loại bỏ các const sẽ gây ra sự mơ hồ, do đó, sử dụng cách StenSoft để làm cho nó hoạt động.

+0

xóa 'const' sẽ dẫn đến sự mơ hồ –

+0

Cảm ơn bạn. Tôi không chắc lắm. Tôi sẽ chỉnh sửa điều đó. – JKor

6

Phương thức không được khai báo là hàm chấp nhận có thể được gọi với đối số string *. Hàm được cung cấp chấp nhận const string *. string * được chuyển đổi hoàn toàn thành const string*. Do đó, hàm có đối số const string * là đối số được chấp nhận cho phương thức không phải const và phương thức không const được chọn vì this cũng không phải là const.

Sử dụng const_cast trên this để sử dụng phương pháp const:

const_cast<const A*>(this)->doStuff(…); 
+0

Câu trả lời rất hay. Tôi có cảm giác rằng tôi đã bỏ lỡ một cái gì đó như thế này, nhưng tôi không thể đặt ngón tay lên đó. Cảm ơn! – Leander

1

Tôi không nghĩ rằng đây là một tình trạng quá tải đúng:

void doStuff(function<void (const string *)> func) const 

void doStuff(function<void (string *)> func) 

chức năng quá tải nên có cùng một nguyên mẫu nhưng các đối số khác nhau. Trong trường hợp của bạn, phương thức const của bạn chỉ có thể được gọi nếu đối tượng của bạn là const. Ở đây bạn chỉ có 2 phương pháp có thể được gọi trong các tình huống khác nhau nhưng những tình huống này không phải do cơ chế overloading hoặc bất kỳ tính năng phụ thuộc đối số nào khác gây ra.

Ngoài ra, tại sao không cho phép mọi người sử dụng đối tượng của bạn với trình lặp vòng mặc định? Thực hiện các phương thức begin()end() trong đối tượng của bạn và cho phép mọi người làm mọi thứ họ muốn mà không có giao diện của bạn nhưng giao diện lib: họ sẽ có thể sử dụng phạm vi, một số thuật toán như findfind_if và những điều tốt khác.

+0

Bạn chính xác rằng đây không phải là quá tải chính thức, cảm ơn vì đã chỉ ra điều đó. Sống và học hỏi. :) Tôi đã xem xét việc thực hiện begin() và end(), nhưng lớp của tôi có nhiều hơn một bộ sưu tập có khả năng được lặp lại và tôi muốn làm rõ điều đó. – Leander

1

Metaprogramming soạn sẵn:

template<template<class...>class Z, class always_void, class...Ts> 
struct can_apply_helper:std::false_type{}; 
template<template<class...>class Z, class...Ts> 
struct can_apply_helper<Z, 
    decltype((void)(Z<Ts...>)), 
Ts...>:std::true_type{}; 
template<template<class...>class Z, class...Ts> 
using can_apply=can_apply_helper<Z,void,Ts...>; 

Trait phát hiện nếu một loại thể hiện sẽ đại diện cho một cuộc gọi hợp lệ:

// result_of_t fails to be SFINAE in too many compilers: 
template<class F, class...Ts> 
using invoke_helper_t=decltype(std::declval<F>()(std::declval<Ts>()...)); 
template<class Sig> struct can_invoke; 
template<class F, class...Ts> 
struct can_invoke<F(Ts...)>: 
    can_apply<invoke_helper_t, F, Ts...> 
{}; 

Bây giờ, thay thế doStuff trong lớp học của bạn. Người đầu tiên phát hiện nếu bạn có thể gọi hàm với một std::string const*:

template<class F, class=std::enable_if_t< 
    can_invoke< F&(std::string const*) >{} 
>> 
void doStuff(F&& func) const 
{ 
    cout << "Const method called" << endl; 
    for(const auto& i_string : m_vec) 
     func(i_string); 
} 

Cái này phát hiện nếu bạn không thể gọi nó với một std::string const* và rằng bạn thể gọi nó với một std::string*:

template<class F, class=std::enable_if_t< 
    !can_invoke< F&(std::string const*) >{} && 
    can_invoke< F&(std::string*) >{} 
>> 
void doStuff(F&& func) 
{ 
    cout << "Non-const method called" << endl; 
    doStuff([&func](const string *str) 
    { 
     auto mutableString = const_cast<string *>(str); 
     func(mutableString); 
    }); 
} 

điều này cũng loại bỏ việc xóa không cần thiết loại std::function trong ví dụ của bạn và định tuyến bất kỳ cuộc gọi nào có thể đi đến phương pháp const theo phương pháp const.

Ngoài ra, việc lưu trữ std::vector<std::string*> hầu như luôn là ý tưởng tồi.

+0

Thành thật mà nói, tôi đã không thực sự nhận được vào các phương pháp lập trình meta tiên tiến hơn, và đây là một chút quá phức tạp đối với tôi ngay bây giờ - và tôi cũng có thể nghĩ đến giải pháp thực tế khác cho vấn đề của tôi, chẳng hạn như chỉ đơn giản là đổi tên các phương pháp. Nhưng bạn đã cho tôi một cái gì đó để suy nghĩ về, cảm ơn bạn! – Leander

+0

@Leander * nod *. Nếu các khái niệm-lite từng làm cho nó thành C++, phần lớn các điều trên trở nên dễ dàng hơn và chúng ta cần nó để làm cho nó dễ dàng hơn. – Yakk

1

Lý do của bạn có thể là lambda có chữ ký void(const string *) không thể được gọi với string * và do đó không được chuyển đổi thành function<void (string *)>. Điều này là không chính xác vì một string * được chuyển đổi hoàn toàn thành const string * và do đó hoàn toàn hợp pháp để xây dựng một đối tượng function<void (string *)> ra khỏi đối tượng hàm mong đợi một const string *. Nó chỉ là trường hợp ngược lại không được phép. Điều này có nghĩa là trình biên dịch xác định rằng cả hai chuyển đổi đối số đều khả thi (với thứ hạng bằng nhau). Điều này sẽ làm cho hai ứng cử viên không rõ ràng về độ phân giải quá tải, nhưng bởi vì điểm số ngụ ý this không phải là const, mức độ không phải là const được ưu tiên (thứ hạng của hàm ẩn this là "khớp chính xác" cho non- const và "chuyển đổi "cho const).

Giải pháp, như đã được đề cập trước đó, là đảm bảo rằng điểm mốc tiềm ẩn thisconst. Điều này sẽ loại bỏ quá tải không const từ nhóm ứng cử viên và buộc một yêu cầu quá tải dự định.

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