6

Trong một nỗ lực để viết một loại wrapper cho một loại T, tôi gặp phải một vấn đề khá khó chịu: Tôi muốn xác định một số nhà khai thác nhị phân (chẳng hạn như +) mà chuyển tiếp bất kỳ hoạt động trên wrapper đến loại cơ bản, nhưng tôi cần những nhà khai thác chấp nhận bất kỳ sự kết hợp tiềm năng có liên quan đến wrapper:đệ quy Infinite với `enable_if`

wrapper() + wrapper() 
wrapper() + T() 
T()  + wrapper() 

cách tiếp cận ngây thơ liên quan đến văn bản tất cả các quá tải tiềm năng trực tiếp.

Nhưng tôi không thích viết mã trùng lặp và muốn thử thách hơn một chút, vì vậy tôi đã chọn triển khai mã đó bằng cách sử dụng mẫu rất chung chung và hạn chế các loại tiềm năng có số enable_if.

Nỗ lực của tôi được hiển thị ở cuối câu hỏi (xin lỗi, điều này là tối thiểu như tôi có thể nghĩ đến). Vấn đề là nó sẽ chạy vào một lỗi đệ quy vô hạn:

  1. Để đánh giá test() + test(), biên dịch xem xét tất cả tình trạng quá tải tiềm năng.
  2. Toán tử được xác định ở đây thực tế là tình trạng quá tải tiềm ẩn, do đó, nó cố gắng tạo kiểu trả về.
  3. Kiểu trả về có enable_if khoản, mà là vụ để ngăn chặn nó từ một tình trạng quá tải hiệu lực, nhưng trình biên dịch chỉ cần bỏ qua điều đó và cố gắng tính toán decltype đầu tiên, đòi hỏi ...
  4. ... một instantiation của operator+(test, test).

Và chúng tôi quay lại nơi chúng tôi bắt đầu. GCC là tốt đẹp, đủ để nhổ một lỗi; Clang chỉ segfaults.

Giải pháp nào là tốt, sạch cho việc này? (Hãy nhớ rằng cũng có những nhà khai thác khác mà cần phải làm theo cùng một khuôn mẫu.)

template<class T> 
struct wrapper { T t; }; 

// Checks if the type is instantiated from the wrapper 
template<class> struct is_wrapper    : false_type {}; 
template<class T> struct is_wrapper<wrapper<T> > : true_type {}; 

// Returns the underlying object 
template<class T> const T& base(const T& t)   { return t; } 
template<class T> const T& base(const wrapper<T>& w) { return w.t; } 

// Operator 
template<class W, class X> 
typename enable_if< 
    is_wrapper<W>::value || is_wrapper<X>::value, 
    decltype(base(declval<W>()) + base(declval<X>())) 
>::type operator+(const W& i, const X& j); 

// Test case 
struct test {}; 
int main() { 
    test() + test(); 
    return 0; 
} 

Dưới đây là giải pháp khá vụng về mà tôi không muốn sử dụng, trừ khi tôi phải:

// Force the evaluation to occur as a 2-step process 
template<class W, class X, class = void> 
struct plus_ret; 
template<class W, class X> 
struct plus_ret<W, X, typename enable_if< 
    is_wrapper<W>::value || is_wrapper<X>::value>::type> { 
    typedef decltype(base(declval<W>()) + base(declval<X>())) type; 
}; 

// Operator 
template<class W, class X> 
typename plus_ret<W, X>::type operator+(const W& i, const X& j); 
+5

tại một số điểm việc chữa bệnh tệ hơn bệnh. Chỉ cần cắn đạn và viết 3 quá tải. – TemplateRex

+0

Sẽ là một điều đáng tiếc nếu đó là trường hợp. Oh well. – Rufflewind

+0

xem câu trả lời của tôi cho 2 phương pháp tiếp cận – TemplateRex

Trả lời

2

Là một Ngoài những nhận xét của TemplateRex, tôi sẽ đề nghị sử dụng một macro để thực hiện tất cả các quá tải và chịu các nhà điều hành như một cuộc tranh cãi:

template<class T> 
struct wrapper { T t; }; 

#define BINARY_OPERATOR(op)          \ 
    template<class T>            \ 
    T operator op (wrapper<T> const& lhs, wrapper<T> const& rhs); \ 
    template<class T>            \ 
    T operator op (wrapper<T> const& lhs, T const& rhs);   \ 
    template<class T>            \ 
    T operator op (T const& lhs, wrapper<T> const& rhs); 

BINARY_OPERATOR(+) 
BINARY_OPERATOR(-) 

#undef BINARY_OPERATOR 

// Test case 
struct test {}; 
test operator+(test const&, test const&); 
test operator-(test const&, test const&); 

int main() { 
    test() + test(); 
    wrapper<test>() + test(); 
    test() - wrapper<test>(); 
    return 0; 
} 
+0

Boost.Operators làm cho điều này an toàn hơn bằng cách sử dụng thừa kế lớp để cung cấp cho các toán tử này – TemplateRex

2

Đây là cái gì đó được đề cập đến trên boost page cho enable_if, trong một tình huống tương tự không mong muốn (mặc dù lỗi mà họ muốn tránh là khác nhau). Giải pháp tăng cường là tạo ra một lớp học lazy_enable_if.

Vấn đề, vì nó là, trình biên dịch sẽ cố gắng để nhanh chóng tất cả các loại có trong chữ ký chức năng, và do đó biểu thức decltype(...) quá. Cũng không đảm bảo rằng điều kiện được tính trước loại.

Rất tiếc, tôi không thể đưa ra giải pháp cho vấn đề này; nỗ lực mới nhất của tôi có thể được nhìn thấy here và vẫn kích hoạt vấn đề độ sâu tức thời tối đa.

1

Cách đơn giản nhất để viết chế độ hỗn hợp số học là làm theo Scott Meyers của mục 24 trong Effective C++

template<class T> 
class wrapper1 
{ 
public: 
    wrapper1(T const& t): t_(t) {} // yes, no explicit here 

    friend wrapper1 operator+(wrapper1 const& lhs, wrapper1 const& rhs) 
    { 
     return wrapper1{ lhs.t_ + rhs.t_ };   
    } 

    std::ostream& print(std::ostream& os) const 
    { 
     return os << t_; 
    } 

private: 
    T t_; 
}; 

template<class T> 
std::ostream& operator<<(std::ostream& os, wrapper1<T> const& rhs) 
{ 
    return rhs.print(os); 
} 

Lưu ý rằng bạn vẫn sẽ cần phải viết operator+= để cung cấp một giao diện phù hợp ("làm như ints làm "). Nếu bạn cũng muốn tránh soạn sẵn đó, hãy nhìn vào Boost.Operators

template<class T> 
class wrapper2 
: 
    boost::addable< wrapper2<T> > 
{ 
public: 
    wrapper2(T const& t): t_(t) {} 

    // operator+ provided by boost::addable 
    wrapper2& operator+=(wrapper2 const& rhs) 
    { 
     t_ += rhs.t_; 
     return *this; 
    }   

    std::ostream& print(std::ostream& os) const 
    { 
     return os << t_; 
    } 

private: 
    T t_; 
}; 

template<class T> 
std::ostream& operator<<(std::ostream& os, wrapper2<T> const& rhs) 
{ 
    return rhs.print(os); 
} 

Trong cả hai trường hợp, bạn có thể viết

int main() 
{ 
    wrapper1<int> v{1}; 
    wrapper1<int> w{2}; 

    std::cout << (v + w) << "\n"; 
    std::cout << (1 + w) << "\n"; 
    std::cout << (v + 2) << "\n"; 

    wrapper2<int> x{1}; 
    wrapper2<int> y{2}; 

    std::cout << (x + y) << "\n"; 
    std::cout << (1 + y) << "\n"; 
    std::cout << (x + 2) << "\n"; 

} 

mà sẽ in 3 trong mọi trường hợp. Live example. Phương pháp Tăng cường rất chung chung, ví dụ: bạn có thể lấy được từ số boost::arithmetic cũng cung cấp operator* từ định nghĩa của bạn về operator*=.

LƯU Ý: mã này dựa trên chuyển đổi ẩn của T đến wrapper<T>. Nhưng để trích dẫn Scott Meyers:

Classes supporting implicit type conversions are generally a bad idea. Of course, there are exceptions to this rule, and one of the most common is when creating numerical types.

+0

Lưu ý rằng 'wrapper v {1}; v + 2; 'sẽ không biên dịch vì' 2' thuộc loại 'int'. Có lẽ các nhà xây dựng nên được templated quá? –

+0

@MatthieuM. vâng, điểm tốt. Nếu các chuyển đổi như vậy nên được cho phép, thì mẫu trình xây dựng chuyển đổi sẽ là tốt nhất. Nhưng tôi nghĩ rằng trong trường hợp đó nó thuận tiện hơn để sử dụng các hình thức hỗn hợp số học của 'boost :: addable ' mà tôi nghĩ rằng cho phép cho điều đó. – TemplateRex

+0

Thật vậy, đó là những gì họ đang có. –

1

Tôi có câu trả lời tốt hơn cho mục đích của bạn: không làm cho nó phức tạp, không sử dụng nhiều chương trình meta. Thay vào đó hãy sử dụng hàm đơn giản để unwrap và sử dụng các biểu thức bình thường. Bạn không cần sử dụng enable_if để xóa các toán tử khỏi tập quá tải chức năng. Nếu chúng không được sử dụng, sẽ không bao giờ cần phải compile và nếu chúng được sử dụng, chúng sẽ cung cấp một ý nghĩa đầy đủ error.

namespace w { 
    template<class T> 
    struct wrapper { T t; }; 

    template<class T> 
    T const& unwrap(T const& t) { 
     return t; 
    } 

    template<class T> 
    T const& unwrap(wrapper<T> const& w) { 
     return w.t; 
    } 

    template<class T1,class T2> 
    auto operator +(T1 const& t1, T2 const& t2) -> decltype(unwrap(t1)+unwrap(t2)) { 
     return unwrap(t1)+unwrap(t2); 
    } 
    template<class T1,class T2> 
    auto operator -(T1 const& t1, T2 const& t2) -> decltype(unwrap(t1)-unwrap(t2)) { 
     return unwrap(t1)-unwrap(t2); 
    } 

    template<class T1,class T2> 
    auto operator *(T1 const& t1, T2 const& t2) -> decltype(unwrap(t1)*unwrap(t2)) { 
     return unwrap(t1)*unwrap(t2); 
    } 
}  

// Test case 
struct test {}; 

test operator+(test const&, test const&); 
test operator-(test const&, test const&); 

int main() { 
    test() + test(); 
    w::wrapper<test>() + w::wrapper<test>(); 

    w::wrapper<test>() + test(); 
    test() - w::wrapper<test>(); 

    return 0; 
} 

Edit:

Là một thông tin bổ sung intersting Tôi có thể nói, các soultion orignal từ fzlogic biên dịch dưới msvc 11 (nhưng không phải 10). Bây giờ giải pháp của tôi (trình bày ở đây) không biên dịch trên cả hai (cho C1045). Nếu bạn cần phải giải quyết những vấn đề này với msvc và gcc (tôi không có clang ở đây), bạn phải di chuyển logic đến các không gian tên và chức năng khác nhau để ngăn trình biên dịch sử dụng ADL và xem xét việc cân nhắc operator+.

+0

Công trình này, với điều kiện là trình bao bọc sống trong không gian tên riêng, được che chở của nó ... một cách giải quyết hợp lý, mặc dù không lý tưởng. – Rufflewind

+0

@fzlogic: bạn có thể kết hợp nó với một 'sử dụng w :: wrapper' trong không gian tên dự định không? Tôi tin rằng 'boost' sử dụng thủ thuật không gian tên riêng này cho một vài lớp và sau đó nhập * chỉ * lớp trong một không gian tên bên ngoài và để nó vào ADL để nhận các hàm đúng. –