2011-05-18 14 views
5

To jest mój (oddzielana) klasy i instancji jednego obiektu:Dlaczego to przeciążenie tego konstruktora jest niepoprawnie rozwiązywane?

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); 

Spodziewam się to do wywołania konstruktora Carray(size_type, const T&, const Allocator&), ale tak nie jest. Apparantly to postanawia do template<typename InputIterator> Carray(InputIterator, InputIterator, const Allocator&).

Co należy zmienić, aby działało zgodnie z przeznaczeniem? Uważam to za dziwne, ponieważ połączenie z std::vector<int> v(5, 10) działa doskonale. A jeśli spojrzeć na definicję konstruktorów w realizacji mojego GCC Uważam to (I przemianowany kilka nazwisk kompilator-wdrożeniowe, jak __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()); 

    // ... 
}; 

który wydaje się być takie same.

Odpowiedz

2

Powinno to działać ze wszystkimi typami iteratorów (w tym wskaźnikami) i aktualnym standardem.

#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)); 
} 

Wyjaśnienie, iterator musi normalnie zdefiniować kilka rodzajów takich jak iterator_category, można skorzystać z tego i sfinae wykryć prawdziwe iteratory.Komplikacja jest taka, że ​​wskaźniki są także iteratorami, ale nie mają zdefiniowanych tych typów (coś, co std::iterator_traits zapewnia specjalizację), więc powyższe podejście jest podobne, jeśli przekazany typ jest wskaźnikiem, to jest domyślnie traktowany jako iterator. Takie podejście pozwala uniknąć konieczności testowania typów integralnych.

Zobacz demo: http://www.ideone.com/E9l1T

+0

Dziękuję, to pozwala mi w pełni zdefiniować mój nagłówek bez polegania na boostu lub nie-C++ 03. Nie użyję tego oczywiście w kodzie produkcji (gdzie boost :: enable_if jest dużo łatwiejszy w użyciu i odpowiedni). – orlp

+0

@ noccracker, bez obaw ... to było ciekawe wyzwanie ... – Nim

7

Wyrażony konstruktor oczekuje wartości size_t i int. Podałeś dwa ints.

Podstawienie int dla InputIterator powoduje, że szablon jest lepiej dopasowany.

Jeśli przyjrzysz się bliżej standardowym pojemnikom, zobaczysz, że używają jakiegoś szablonu meta-programowania, aby określić, czy InputIterator może być rzeczywistym iteratorem, czy też jest to typ całkowity. To następnie przekierowuje do innej konstrukcji.

Edit
Oto jeden sposób to zrobić:

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()); } 

Można również użyć boost :: type_traits jeśli kompilator nie posiada std :: type_traits.

+1

Jak to rozwiązać? I konstruktor 'vector' oczekuje również' size_t' i 'int', ale podczas przekazywania' int, int' nadal będzie działał na "poprawny". – orlp

+0

@nightcracker - Moja odpowiedź nie była jeszcze gotowa ... –

+1

@noccracker: 'std :: vector' zachowuje się w ten sposób, ponieważ standard wymaga, aby zachowywał się w ten sposób. Jest to dodatkowy wymóg nakładany przez standard na podstawowe zachowanie języka podstawowego. Jeśli chcesz, aby twoja klasa zachowywała się w ten sam sposób, będziesz musiał podjąć dodatkowe kroki (tak jak robi to 'std :: vector'). Możesz rzucić okiem na konkretną implementację 'std :: vector', aby zobaczyć, jak się to odbywa. – AnT

3

Spróbuj tego. Wyeliminuje konstruktora iteratora z uwzględnieniem jeśli dwa ints są przekazywane:

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

referencyjny: http://www.boost.org/doc/libs/1_46_1/libs/utility/enable_if.html


EDIT: odpowiedzi na „Czy istnieje jakiś sposób, z zaledwie 03 C++ i STL bez doładowania? "

Nie wiem, czy std :: type_traits jest w C++ 03 czy nie - zawsze mam doładowanie, więc po prostu go używam. Ale możesz spróbować tego. Będzie ona działać w tym konkretnym przypadku, ale nie może mieć ogólności można wymagać:

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

Czy jest jakiś sposób z tylko C++ 03 STL i bez doładowania? Nie chodzi o to, że nie chcę używać boostu, ale chcę, aby ten plik nagłówkowy był przenośny i jeśli to możliwe, unikał doładowania. – orlp

+0

@ noccracker: Możesz po prostu napisać własną. Typy, takie jak 'is_integral', są dość trywialne do określenia. – Puppy

0

Pierwszy konstruktor oczekuje argument „wartość”, aby być przekazywane przez referencję, natomiast drugi konstruktor spodziewa się, że pierwsze 2 Wartości być przekazane według wartości. Z mojego doświadczenia wynika, że ​​C++ jest dość rygorystyczne w odniesieniu do tego rozróżnienia, spróbuj przekazać zmienną całkowitą zamiast wartości całkowitej jako drugi argument do konstruktora obiektu.

Powiązane problemy