2011-05-18 13 views
5

Questo è il mio (spogliata) di classe e istanza di un oggetto:Perché questo overload del costruttore si risolve in modo errato?

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

mi si aspetterebbe questo per chiamare il costruttore Carray(size_type, const T&, const Allocator&), ma non è così. Apparentemente questo si risolve a template<typename InputIterator> Carray(InputIterator, InputIterator, const Allocator&).

Cosa devo cambiare per fare in modo che funzioni come previsto? Lo trovo strano anche perché una chiamata a std::vector<int> v(5, 10) funziona perfettamente bene. E se guardo la definizione dei costruttori nella realizzazione del mio GCC ho trovato questo (ho rinominato alcuni nomi compilatore-implementazione, come __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()); 

    // ... 
}; 

che sembra essere lo stesso.

risposta

2

Questo dovrebbe funzionare con tutti i tipi di iteratore (compresi i puntatori) e lo standard corrente.

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

spiegazione, un iteratore deve normalmente definire diversi tipi come iterator_category, e si può approfittare di questo e sfinae per rilevare iteratori reali.La complicazione è che i puntatori sono anche iteratori, ma non hanno questi tipi definiti (qualcosa per cui la specializzazione è std::iterator_traits), quindi il metodo sopra segue un approccio simile, se il tipo passato è un puntatore, quindi viene considerato come predefinito iteratore. Questo approccio ti evita di dover testare per i tipi interi.

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

+0

Grazie, questo mi consente di definire completamente la mia intestazione senza fare affidamento su boost o non C++ 03. Non lo userò ovviamente nel codice di produzione (dove boost :: enable_if è molto più facile da usare e appropriato). – orlp

+0

@nightcracker, nessuna preoccupazione ... è stata una sfida interessante ... – Nim

7

Il costruttore esplicito si aspetta un size_t e un int. Hai fornito due int.

Sostituireper InputIterator rende il modello una corrispondenza migliore.

Se si osservano più da vicino i contenitori standard, si noterà che utilizzano alcuni modelli di meta-programmazione per determinare se lo InputIterator potrebbe essere un vero iteratore o se è un tipo intero. Questo quindi reindirizza a una diversa costruzione.

Modifica
Ecco un modo per farlo:

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

Si potrebbe anche usare boost :: type_traits se il compilatore non ha std :: type_traits.

+1

E come vorrei risolvere questo? E il costruttore 'vector' si aspetta anche un' size_t' e un 'int', ma mentre viene passato' int, int' si risolverà comunque in quello "corretto". – orlp

+0

@nightcracker - La mia risposta non era ancora pronta ... –

+1

@nightcracker: 'std :: vector' si comporta in questo modo perché lo standard richiede che si comporti in quel modo. È un requisito aggiuntivo imposto dallo standard in aggiunta al comportamento linguistico di base della lingua. Se vuoi che la tua classe si comporti allo stesso modo, dovrai fare dei passi extra (proprio come fa 'std :: vector'). Puoi dare un'occhiata a un'implementazione specifica di 'std :: vector' per vedere come viene eseguita lì. – AnT

3

Prova questo. Sarà eliminare il costruttore iteratore dalla considerazione se sono passati due interi:

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

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


EDIT: rispondere a "C'è un modo solo con la C++ 03 STL e senza spinta? "

Non so se std :: type_traits è in C++ 03 o no - ho sempre disponibile boost, quindi lo uso. Ma puoi provare questo. Si lavorerà in questo caso specifico, ma non può avere la generalità si richiede:

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'è un modo solo con il C++ 03 STL e senza boost? Non è che non voglio usare boost, ma voglio che questo file di intestazione sia portatile ed eviti il ​​boost se possibile. – orlp

+0

@nightcracker: puoi solo scrivere il tuo. Digitare tratti come 'is_integral' sono relativamente banali da specificare. – Puppy

0

Il primo costruttore si aspetta che l'argomento 'valore' da passare per riferimento, mentre il secondo costruttore si aspetta che i primi 2 valori per essere passato per valore. Nella mia esperienza, C++ è piuttosto severo su questa distinzione, prova a passare una variabile intera invece di un valore intero come secondo argomento al costruttore dell'oggetto.

Problemi correlati