2011-05-18 7 views
5

Đây là của tôi (tước) lớp và instantiation của một đối tượng:Tại sao quá trình xây dựng quá tải này giải quyết không chính xác?

template <typename T, typename Allocator = std::allocator<T> > 
class Carray { 
    typedef typename Allocator::size_type size_type; 

    // ... 

    explicit Carray(size_type n, const T& value, const Allocator& alloc = Allocator()) { 
     // ... 
    } 

    template<typename InputIterator> 
    Carray(InputIterator first, InputIterator last, const Allocator& alloc = Allocator()) { 
     // ... 
    } 

    // ... 
} 

Carray<int> array(5, 10); 

tôi sẽ mong đợi điều này để gọi constructor Carray(size_type, const T&, const Allocator&), nhưng nó không. Bảo mật điều này giải quyết thành template<typename InputIterator> Carray(InputIterator, InputIterator, const Allocator&).

Tôi nên thay đổi gì để thực hiện công việc này như dự định? Tôi tìm thấy nó cũng lạ, bởi vì một cuộc gọi đến std::vector<int> v(5, 10) hoạt động hoàn toàn tốt đẹp. Và nếu tôi nhìn vào định nghĩa của các nhà thầu trong việc thực hiện GCC của tôi Tôi tìm thấy điều này (tôi đổi tên một số tên biên dịch-thực hiện, như __n):

template<typename T, typename A = std::allocator<T> > 
class vector { 
    typedef size_t size_type; 
    typedef T value_type; 
    typedef A allocator_type; 

    // ... 

    explicit vector(size_type n, const value_type& value = value_type(), const allocator_type& a = allocator_type()); 

    template<typename InputIterator> 
    vector(InputIterator first, InputIterator last, const allocator_type& a = allocator_type()); 

    // ... 
}; 

mà dường như không thay đổi.

Trả lời

2

Điều này sẽ hoạt động với tất cả các loại trình lặp (bao gồm con trỏ) và tiêu chuẩn hiện tại.

#include <iostream> 
#include <iterator> 
#include <vector> 

// uses sfinae to determine if the passed in type is indeed an iterator 
template <typename T> 
struct is_iterator_impl 
{ 
    typedef char yes[1]; 
    typedef char no[2]; 

    template <typename C> 
    static yes& _test(typename C::iterator_category*); 

    template <typename> 
    static no& _test(...); 

    static const bool value = sizeof(_test<T>(0)) == sizeof(yes); 
}; 

template <typename T, bool check = is_iterator_impl<T>::value> 
struct is_iterator 
{ 
    typedef void type; 
}; 

template <typename T> 
struct is_iterator<T, false> 
{ 
}; 

template <typename T> 
struct is_iterator<T*, false> 
{ 
    typedef void type; 
}; 

template <typename T> 
struct foo 
{ 
    explicit foo(std::size_t n, const T& value) 
    { 
    std::cout << "foo:size_t" << std::endl; 
    } 

    template<typename InputIterator> 
    foo(InputIterator first, InputIterator last, typename is_iterator<InputIterator>::type* dummy = 0) 
    { 
    std::cout << "foo::iterator" << std::endl; 
    } 
}; 

int main(void) 
{ 
    // should not cause a problem 
    foo<int> f(1, 2); 

    // using iterators is okay 
    typedef std::vector<int> vec; 
    vec v; 
    foo<int> b(v.begin(), v.end()); 

    // using raw pointers - is okay 
    char bar[] = {'a', 'b', 'c'}; 
    foo<char> c(bar, bar + sizeof(bar)); 
} 

Giải thích, một iterator thường phải xác định một số dạng như iterator_category, và bạn có thể tận dụng điều này và sfinae để phát hiện vòng lặp thực.Biến chứng là các con trỏ cũng là các biến lặp, nhưng không có các kiểu này được xác định (một cái gì đó std::iterator_traits cung cấp một chuyên môn), vì vậy ở trên có cách tiếp cận tương tự, nếu kiểu được truyền là một con trỏ, thì nó được mặc định coi là một trình lặp. Cách tiếp cận này giúp bạn phải kiểm tra các loại tích phân.

Xem bản trình diễn: http://www.ideone.com/E9l1T

+0

Cảm ơn bạn, điều này cho phép tôi xác định đầy đủ tiêu đề của mình mà không dựa vào việc tăng hoặc không phải C++ 03. Tôi sẽ không sử dụng điều này trong mã sản phẩm của khóa học (nơi boost :: enable_if dễ sử dụng hơn và phù hợp hơn). – orlp

+0

@nightcracker, không phải lo lắng ... đó là một thử thách thú vị ... – Nim

7

Trình xây dựng rõ ràng mong đợi một size_t và một int. Bạn đã cung cấp hai ints.

Thay thế int cho InputIterator làm cho mẫu phù hợp hơn.

Nếu bạn nhìn kỹ hơn các thùng chứa tiêu chuẩn, bạn sẽ thấy rằng họ sử dụng một số mẫu siêu lập trình để xác định xem InputIterator có thể là một trình lặp thực hay không. Điều này sau đó chuyển hướng đến một cấu trúc khác.

Sửa
Dưới đây là một cách để làm việc đó:

template<class _InputIterator> 
    vector(_InputIterator _First, _InputIterator _Last, 
     const allocator_type& _Allocator = allocator_type()) 
    : _MyAllocator(_Allocator), _MyBuffer(nullptr), _MySize(0), _MyCapacity(0) 
    { _Construct(_First, _Last, typename std::is_integral<_InputIterator>::type()); } 

private: 
    template<class _IntegralT> 
    void _Construct(_IntegralT _Count, _IntegralT _Value, std::true_type /* is_integral */) 
    { _ConstructByCount(static_cast<size_type>(_Count), _Value); } 

    template<class _IteratorT> 
    void _Construct(_IteratorT _First, _IteratorT _Last, std::false_type /* !is_integral */) 
    { _Construct(_First, _Last, typename std::iterator_traits<_IteratorT>::iterator_category()); } 

Bạn cũng có thể sử dụng tăng :: type_traits nếu trình biên dịch không có std :: type_traits.

+1

Và làm cách nào để giải quyết điều đó? Và hàm tạo 'vector' mong muốn một' size_t' và 'int' quá, nhưng trong khi đã truyền' int, int' nó sẽ vẫn giải quyết thành "đúng". – orlp

+0

@nightcracker - Câu trả lời của tôi không hoàn toàn sẵn sàng ... –

+1

@nightcracker: 'std :: vector' hoạt động theo cách đó vì tiêu chuẩn yêu cầu nó hoạt động theo cách đó. Đó là một yêu cầu bổ sung áp đặt bởi tiêu chuẩn trên đầu trang của hành vi ngôn ngữ cốt lõi cơ bản. Nếu bạn muốn lớp của bạn cư xử theo cùng một cách, bạn sẽ phải thực hiện thêm các bước (giống như 'std :: vector'). Bạn có thể xem xét một triển khai cụ thể của 'std :: vector' để xem nó được thực hiện ở đó như thế nào. – AnT

3

Hãy thử điều này. Nó sẽ loại bỏ các nhà xây dựng iterator từ xem xét nếu hai ints được thông qua:

template<typename InputIterator> 
Carray(InputIterator first, InputIterator last, 
    const Allocator& alloc = Allocator(), 
    typename boost::disable_if<boost::is_integral<InputIterator> >::type* dummy = 0) { 
} 

tham khảo: http://www.boost.org/doc/libs/1_46_1/libs/utility/enable_if.html


EDIT: hưởng ứng "Có cách nào chỉ với C++ 03 STL và mà không tăng? "

Tôi không biết nếu std :: type_traits có trong C++ 03 hay không - tôi luôn luôn có sẵn tăng, vì vậy tôi chỉ sử dụng nó. Nhưng bạn có thể thử điều này. Nó sẽ làm việc trong trường hợp cụ thể này, nhưng có thể không có tính tổng quát bạn cần:

template <class T> class NotInt { typedef void* type; }; 
template <> class NotInt<int> { }; 

template <typename T, typename Allocator = std::allocator<T> > 
class Carray { 
    ... 
    template<typename InputIterator> 
    Carray(InputIterator first, InputIterator last, 
     const Allocator& alloc = Allocator(), 
     typename NotInt<InputIterator>::type t = 0) { 
    std::cout << __PRETTY_FUNCTION__ << "\n"; 
    } 
}; 
+0

Có cách nào chỉ với C++ 03 STL và không tăng cường không? Nó không phải là tôi không muốn sử dụng tăng, nhưng tôi muốn tập tin tiêu đề này được di động và tránh tăng nếu có thể. – orlp

+0

@nightcracker: Bạn chỉ có thể viết của riêng bạn. Loại đặc điểm như 'is_integral' là tương đối nhỏ để xác định. – Puppy

0

Các nhà xây dựng đầu tiên dự kiến ​​sẽ lập luận 'giá trị' được thông qua bằng cách tham chiếu, trong khi các nhà xây dựng thứ hai hy vọng 2 giá trị đầu tiên được được truyền theo giá trị. Theo kinh nghiệm của tôi, C++ khá nghiêm ngặt về sự khác biệt này, hãy thử truyền một biến số nguyên thay vì một giá trị số nguyên làm đối số thứ 2 cho hàm tạo đối tượng của bạn.