2011-05-18 7 views
5

Ésta es mi (pelado) clase y creación de instancias de un objeto:¿Por qué esta sobrecarga de constructor se resuelve incorrectamente?

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

Yo esperaría que esto llama al constructor Carray(size_type, const T&, const Allocator&), pero no es así. Aparentemente esto resuelve a template<typename InputIterator> Carray(InputIterator, InputIterator, const Allocator&).

¿Qué debo cambiar para que esto funcione según lo previsto? También me parece raro, porque una llamada al std::vector<int> v(5, 10) funciona perfectamente bien. Y si miro a la definición de los constructores en la ejecución de mi GCC Me parece (Retitulé algunos nombres compilador de ejecución, como __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()); 

    // ... 
}; 

que parece ser la misma.

Respuesta

2

Esto debería funcionar con todos los tipos de iteradores (incluidos los punteros) y el estándar actual.

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

Explicación, un iterador debe definir normalmente varios tipos, tales como iterator_category, y usted puede tomar ventaja de esto y sfinae para detectar iteradores reales.La complicación es que los punteros también son iteradores, pero no tienen estos tipos definidos (algo std::iterator_traits proporciona una especialización para), por lo que lo anterior toma un enfoque similar, si el tipo pasado es un puntero, entonces se trata por defecto como un iterador. Este enfoque le ahorra tener que probar para tipos integrales.

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

+0

Gracias, esto me permite definir completamente mi encabezado sin depender de boost o non-C++ 03. No usaré esto en el código de producción, por supuesto (donde boost :: enable_if es mucho más fácil de usar y apropiado). – orlp

+0

@nightcracker, sin preocupaciones ... fue un desafío interesante ... – Nim

7

El constructor explícito espera un tamaño_t y un int. Has proporcionado dos ints.

Sustituyendo int por InputIterator hace que la plantilla sea una mejor coincidencia.

Si observa más de cerca los contenedores estándar, verá que usan meta-programación de plantillas para determinar si el InputIterator podría ser un iterador real o si es un tipo entero. Esto luego redirige a una construcción diferente.

Editar
Esta es una manera de hacerlo:

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

También es posible usar boost :: type_traits si el compilador no tiene std :: type_traits.

+1

¿Y cómo iba a resolver eso? Y el constructor 'vector' también espera' size_t' y 'int', pero mientras pasa' int, int' se resolverá a la "correcta". – orlp

+0

@nightcracker - Mi respuesta no estaba lista ... –

+1

@nightcracker: 'std :: vector' se comporta de esa manera porque el estándar requiere que se comporte de esa manera. Es un requisito adicional impuesto por el estándar además del comportamiento básico del lenguaje central. Si quieres que tu clase se comporte de la misma manera, tendrás que tomar pasos adicionales (como lo hace 'std :: vector'). Puede ver una implementación específica de 'std :: vector' para ver cómo se hace allí. – AnT

3

Pruebe esto. Se eliminará el constructor iterador de la consideración si se pasan dos enteros:

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

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


EDITAR: respondiendo a "¿Hay alguna manera con sólo el C++ 03 y STL sin impulso? "

No sé si std :: type_traits está en C++ 03 o no - Siempre tengo boost disponible, así que solo lo uso. Pero puedes intentar esto. Se trabajará en este caso específico, pero puede no tener la generalidad que necesita:

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

¿Hay alguna manera con solo el C++ 03 STL y sin impulso? No es que no quiera usar boost, pero quiero que este archivo de cabecera sea portátil y evite aumentar si es posible. – orlp

+0

@nightcracker: Puedes escribir el tuyo. Los rasgos de tipo como 'is_integral' son relativamente triviales para especificar. – Puppy

0

El primer constructor espera que el argumento 'valor' que se pasa por referencia, mientras que el segundo constructor espera que los 2 primeros valores sean pasado por valor. En mi experiencia, C++ es bastante estricto con esta distinción, intente pasar una variable entera en lugar de un valor entero como el segundo argumento para su constructor de objetos.

Cuestiones relacionadas