2011-09-05 33 views
6

Tôi đang sử dụng lớp của mình làm tham số mẫu của một lớp cha và lớp cha sử dụng nó trong đối số mẫu (mặc dù sizeof()).Sử dụng lớp con làm tham số mẫu của một lớp cơ sở và như một tham số tên lồng nhau

Và trình biên dịch mang lại cho tôi:

lỗi: loại không đầy đủ 'Invoker :: workerClass {aka MyClass}' được sử dụng trong tên lồng nhau specifier

Tuy nhiên, lớp được được xác định rõ trong file . Tôi đoán điều này là bởi vì lớp trẻ không được khởi tạo tại thời điểm khởi tạo của lớp cơ sở, nhưng điều đó xảy ra với CRTP và không có vấn đề gì.

Lý do tôi sử dụng lớp con trong đối số mẫu là thực hiện cuộc gọi hàm khác nếu lớp con có hoặc không có hàm cụ thể.

Dưới đây là một mã tối thiểu để thử nghiệm

/* Structure similar to boost's enable if, to use 
    SFINAE */ 
template <int X=0, class U = void> 
struct test { 
    typedef U type; 
}; 

enum Commands { 
    Swim, 
    Fly 
}; 

/* Structure used for template overloading, 
    as no partial function template specialization available */ 
template<Commands T> 
struct Param { 

}; 

template <class T> 
class Invoker 
{ 
public: 
    typedef T workerClass; 

    workerClass *wc() { 
     return static_cast<workerClass*>(this); 
    } 

    template <Commands command> 
    void invoke() { 
     invoke2(Param<command>()); 
    } 

    /* If the child class has those functions, call them */ 
    /* Needs template paramter Y to apply SFINAE */ 
    template<class Y=int> 
    typename test<sizeof(Y)+sizeof(decltype(&workerClass::fly))>::type 
    invoke2(Param<Fly>) { 
     wc()->fly(); 
    } 

    template<class Y=int> 
    typename test<sizeof(Y)+sizeof(decltype(&workerClass::swim))>::type 
    invoke2(Param<Swim>) { 
     wc()->shoot(); 
    } 

    template<Commands command> 
    void invoke2(Param<command>) { 
     /* Default action */ 
     printf("Default handler for command %d\n", command); 
    } 
}; 

template <class T, class Inv = Invoker<T> > 
class BaseClass : public Inv 
{ 
public: 
    template<Commands command> 
    void invoke() { 
     Inv::template invoke<command>(); 
    } 
}; 

class MyClass : public BaseClass<MyClass> 
{ 
public: 
    void swim() { 
     printf("Swimming like a fish!\n"); 
    } 

    /* void fly(); */ 
}; 


void testing() { 
    MyClass foo; 
    foo.invoke<Fly>(); /* No 'void fly()' in MyClass, calls the default handler */ 
    foo.invoke<Swim>(); /* Should print the swimming message */ 
} 

Các lỗi xảy ra tại dòng:

typename test<sizeof(Y)+sizeof(decltype(&workerClass::fly))>::type 

Vì vậy, là có bất kỳ trình biên dịch hỗ trợ này, hoặc là này explicitely xác định bởi các tiêu chuẩn như sử dụng mẫu không hợp lệ? Tôi có phải thay đổi cách tôi đang làm điều này và tìm một cách xung quanh? CRTP đang cho tôi hy vọng mã có thể hợp lệ, nhưng tôi không chắc chắn.

Nếu điều này thực sự là không thể, thì tại sao chính xác và tại sao CRTP hoạt động?

+4

Vâng, vấn đề là rõ ràng ít nhất là: Bạn đang làm cho một định nghĩa đệ quy tự tham chiếu: 'BaseClass 'đòi hỏi sự hoàn hảo' MyClass' vì của' decltype 'expression, nhưng' MyClass' yêu cầu một 'BaseClass ' đầy đủ như là một lớp cơ sở. –

+1

Bạn cần phải ẩn các chuyên môn đằng sau một lớp bổ sung của indirection để chúng không được đánh giá như là một phần của định nghĩa lớp cơ sở. Đặt chúng trong một loại lồng nhau tư nhân và tất cả sẽ làm việc tốt. – ildjarn

+0

CRTP là khó khăn vì lý do chính xác này, vấn đề bạn đang phải đối mặt là dễ hiểu: mẫu cơ sở được khởi tạo như một phần của khai báo lớp, tại thời điểm chưa hoàn thành (nghĩ rằng kiểu vẫn phụ thuộc vào cái gì mẫu cơ sở có thể thêm vào, do đó không có cách nào trình biên dịch có thể biết được kiểu dẫn xuất sẽ giống như thế nào nếu không xử lý mẫu cơ bản trước tiên - cơ sở đó có thể thêm các thuộc tính thành viên thay đổi kích thước hoặc các hàm thành viên ảo có thể thay đổi ý nghĩa của các khai báo hàm trong kiểu dẫn xuất) –

Trả lời

2

Giải pháp là, như ildjarn đã chỉ ra, để thêm một mức độ khác.

đó được thực hiện bằng cách thay đổi các chức năng thử nghiệm để chấp nhận các loại:

template <typename X, class U = void> 
struct test { 
    typedef U type; 
}; 

Và sau đó vượt qua các lớp con như một tham số mẫu, thay vì quy định cụ thể nó ngay từ đầu đi:

template<class Y=workerClass> 
    typename test<decltype(&Y::fly)>::type 
    invoke2(Param<Fly>) { 
     wc()->fly(); 
    } 

    template<class Y=workerClass> 
    typename test<decltype(&Y::swim)>::type 
    invoke2(Param<Swim>) { 
     wc()->swim(); 
    } 

Bằng cách đó, chỉ số lồng nhau được đánh giá chỉ khi hàm được gọi và không được đánh giá lớp, và vào thời điểm đó lớp con đã được đánh giá. Ngoài ra, với khả năng truyền đối số mẫu mặc định, chúng ta có thể gọi hàm mà không có bất kỳ tham số mẫu nào.

Mẫu bây giờ cũng dễ đọc hơn nhiều. Và mẫu mã làm việc tốt bây giờ:

class MyClass : public BaseClass<MyClass> 
{ 
public: 
    void swim() { 
     printf("Swimming like a fish!\n"); 
    } 

    /* void fly(); */ 
}; 


void testing() { 
    MyClass foo; 
    foo.invoke<Fly>(); /* No 'void fly()' in MyClass, calls the default handler */ 
    foo.invoke<Swim>(); /* Should print the swimming message */ 
} 
+0

Điều này rất thú vị. Bất cứ ai có thể cung cấp một trường hợp sử dụng thú vị cho điều này? – Zhro

+0

@ Zhro cách tôi sử dụng nó là tạo một hệ thống giống như các hàm ảo mà không có chi phí (không đáng kể) do bảng ảo gây ra. – coyotte508

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