Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

choose which constructor in c++

I am practicing C++ by building my own version of vector, named "Vector". I have two constructors among others, fill constructor and range constructor. Their declarations look like following:

template <typename Type> 
class Vector {
public:
    // fill constructor
    Vector(size_t num, const Type& cont);

    // range constructor
    template<typename InputIterator> Vector(InputIterator first, InputIterator last);

    /* 
    other members
    ......
    */
}

The fill constructor fills the container with num of val; and the range constructor copies every value in the range [first, last) into the container. They are supposed to be the same with the two constructors of the STL vector.

Their definitions goes following:

//fill constructor 
template <typename Type> 
Vector<Type>::Vector(size_t num, const Type& cont){
    content = new Type[num];
    for (int i = 0; i < num; i ++)
        content[i] = cont;
    contentSize = contentCapacity = num;
}

// range constructor
template <typename Type> 
template<typename InputIterator>
Vector<Type>::Vector(InputIterator first, InputIterator last){
    this->content = new Type[last - first];

    int i = 0;
    for (InputIterator iitr = first; iitr != last; iitr ++, i ++)
    *(content + i) = *iitr;

    this->contentSize = this->contentCapacity = i;
}

However, when I try to use them, I have problem distinguishing them. For example:

Vector<int> v1(3, 5);

With the this line of code, I intended to create a Vector that contains three elements, each of which is 5. But the compiler goes for the range constructor, treating both "3" and "5" as instances of the "InputIterator", which, with no surprises, causes error.

Of course, if I change the code to:

Vector<int> v1(size_t(3), 5);

Everything is fine, the fill constructor is called. But that is obviously not intuitive and user friendly.

So, is there a way that I can use the fill constructor intuitively?

like image 687
seemuch Avatar asked Dec 26 '12 02:12

seemuch


2 Answers

You can use std::enable_if (or boost::enable_if if you don't use C++11) to disambiguate the constructors.

#include <iostream>
#include <type_traits>
#include <vector>
using namespace std;


template <typename Type> 
class Vector {
public:
    // fill constructor
    Vector(size_t num, const Type& cont)
    { 
        cout << "Fill constructor" << endl;
    }

    // range constructor
    template<typename InputIterator> Vector(InputIterator first, InputIterator last,
        typename std::enable_if<!std::is_integral<InputIterator>::value>::type* = 0)
    { 
        cout << "Range constructor" << endl;
    }

};

int main()
{
    Vector<int> v1(3, 5);
    std::vector<int> v2(3, 5);
    Vector<int> v3(v2.begin(), v2.end());
}


The above program should first call the fill constructor by checking if the type is an integral type (and thus not an iterator.)


By the way, in your implementation of the range constructor, you should use std::distance(first, last) rather than last - first. Explicitly using the - operator on iterators limits you to RandomAccessIterator types, but you want to support InputIterator which is the most generic type of Iterator.

like image 63
Charles Salvia Avatar answered Sep 30 '22 07:09

Charles Salvia


Even std::vector seems to have this issue.

std::vector<int> v2(2,3);

chooses

template<class _Iter>
        vector(_Iter _First, _Iter _Last)

In Visual C++, even though it should match closer to the non templated case..

Edit: That above function (correctly) delegates the construction to the below one. I am totally lost..

template<class _Iter>
        void _Construct(_Iter _Count, _Iter _Val, _Int_iterator_tag)

Edit #2 AH!:

Somehow this below function identifies which version of the constructor is meant to be called.

template<class _Iter> inline
    typename iterator_traits<_Iter>::iterator_category
        _Iter_cat(const _Iter&)
    {   // return category from iterator argument
    typename iterator_traits<_Iter>::iterator_category _Cat;
    return (_Cat);
    }

The above shown _Construct function has (atleast) 2 versions overloading on the third variable which is a tag to returned by the above _Iter_cat function. Based on the type of this category the correct overload of the _Construct is picked.

Final edit: iterator_traits<_Iter> is a class that seems to be templated for many different common varieties, each returning the appropriate "Category" type

Solution: It appears template specialization of the first arguement's type is how the std library handles this messy situation (primitive value type) in the case of MS VC++. Perhaps you could look into it and follow suit?

The problem arises (I think) because with primitive value types, the Type and size_t variables are similar, and so the template version with two identical types gets picked.

like image 38
Karthik T Avatar answered Sep 30 '22 06:09

Karthik T