seemuch
seemuch

Reputation: 643

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?

Upvotes: 13

Views: 4141

Answers (4)

Charles Salvia
Charles Salvia

Reputation: 53299

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.

Upvotes: 12

AnT stands with Russia
AnT stands with Russia

Reputation: 320531

The problem is the same at the one faced by the standard library implementation. There are several ways to solve it.

  • You can meticulously provide non-template overloaded constructors for all integral types (in place of the first parameter).

  • You can use SFINAE-based technique (like enable_if) to make sure the range constructor is not selected for integer argument.

  • You can branch the range constructor at run-time (by using if) after detecting integral argument (by using is_integral) and redirect control to the proper constructing code. The branching condition will be a compile-time value, meaning that the code will probably be reduced at compile-time by the compiler.

  • You can simply peek into your version of standard library implementation and see how they do it (although their approach is not required to be portable and/or valid from the point of view of abstract C++ language).

Upvotes: 3

Potatoswatter
Potatoswatter

Reputation: 137830

This ambiguity caused problems for early library implementers. It's called the "Do The Right Thing" effect. As far as I know, you need SFINAE to solve it… it might have been one of the first applications of that technique. (Some compilers cheated and hacked their overload resolution internals, until the solution was found within the core language.)

The standard specification of this issue is one of the key differences between C++98 and C++03. From C++11, §23.2.3:

14 For every sequence container defined in this Clause and in Clause 21:

— If the constructor

       template <class InputIterator>
       X(InputIterator first, InputIterator last,
         const allocator_type& alloc = allocator_type())

is called with a type InputIterator that does not qualify as an input iterator, then the constructor shall not participate in overload resolution.

15 The extent to which an implementation determines that a type cannot be an input iterator is unspecified, except that as a minimum integral types shall not qualify as input iterators.

Upvotes: 2

Karthik T
Karthik T

Reputation: 31952

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.

Upvotes: 3

Related Questions