Home > Blockchain >  How to overload normal function and template function with same number of parameters?
How to overload normal function and template function with same number of parameters?

Time:07-18

What am I doing?

I am trying to implement the standard library vector. There is a member function called assign() that is overloaded as:

template<typename InputIterator>
void assign(InputIterator first, InputIterator last)
void assign(size_type n, const value_type& val)
void assign(std::initializer_list<value_type> il)

My implementation:

template<typename InputIterator>
void assign(InputIterator first, InputIterator last)
{
    difference_type t_ptrdiff = last - first;
    if(t_ptrdiff > m_size) reallocate(size_type(t_ptrdiff) << 1);
    for(size_type idx = 0; first != last;   idx,   first)
        m_base[idx] = *first;
    m_size = t_ptrdiff;
}

void assign(size_type n, const value_type& val)
{
    if(n > m_size) reallocate(n << 1);
    for(size_type idx = 0; idx != n;   idx)
        m_base[idx] = val;
    m_size = n;
}

void assign(std::initializer_list<value_type> il)
{
    typename std::initializer_list<value_type>::size_type il_size = il.size();
    typename std::initializer_list<value_type>::iterator end = il.end();
    if(il_size > m_size) reallocate(il_size << 1);
    size_type idx = 0;
    for(typename std::initializer_list<value_type>::iterator begin = il.begin(); begin != end;   idx,   begin)
        m_base[idx] = *begin;
    m_size = il_size;
}

Also, I am pretty sure this is a terrible way to reallocate heap memory, but this is what I currently have:

void reallocate(size_type requested_capacity)
    {
        value_type* t_base = new value_type[requested_capacity];
        m_capacity = requested_capacity;
        if(m_base == nullptr){
            m_base = t_base;
            return;
        }
        if(m_capacity < m_size)
            m_size = m_capacity;
        for(size_type idx = 0; idx < m_size;   idx)
            t_base[idx] = m_base[idx];
        delete [] m_base;
        m_base = t_base;
    }

Problem:

Everything works as expected when I call these methods using integral objects, such as:

vector<int> v;

// This will correctly call: void assign(size_type n, const value_type& val)
vector<int>::size_type n = 7;
vector<int>::value_type val = 100;
v.assign (n, val);

// This will correctly call: void assign(InputIterator first, InputIterator last)
vector<int>::iterator it = v.begin()   1;
v.assign (it, v.end()-1);

// This will correctly call: void assign(std::initializer_list<value_type> il)
v.assign ({1776,7,4});

But when I try to call assign() with integral literals, for example: v.assign(7, 100), the compiler complains with the following error because it is matching against the templated function: void assign(InputIterator first, InputIterator last):

In file included from main.cpp:LINE:
vector.h: In instantiation of ‘void vector<T>::assign(InputIterator, InputIterator) [with InputIterator = int; T = int]’:
main.cpp:LINE:COLUMN:   required from here
vector.h:LINE:COLUMN: error: invalid type argument of unary ‘*’ (have ‘int’)
  xxx |             m_base[idx] = *first;

libstdc implementation:

I checked libstdc implementation, and this is their function declarations:

void assign(size_type __n, const value_type& __val);
template<typename _InputIterator>
void assign(_InputIterator __first, _InputIterator __last);
void assign(initializer_list<value_type> __l);

With the standard library's std::vector there is no such conflict; v.assign(7, 100) is correctly resolved to void assign(size_type __n, const value_type& __val); but not in my implementation.

CodePudding user response:

The overload

template <typename InputIterator>
void assign(InputIterator first, InputIterator last)

participates in overload resolution only if InputIterator satisfies LegacyInputIterator see.

For an implementor this means that some kind of SFINAE is necessary pre-C 20. In C 20 you can use concepts instead.

To SFINAE away non-iterators, you can do something like this

template <typename InputIterator>
std::enable_if_t<???> assign ...

where ??? is a constant expression that is only valid (and true) for InputIterator which is a LegacyInputIterator.

I will not write down the entire condition, but here is one part of it. To ensure that the dereference is defined for InputIterator, you can write

sizeof(*std::declval<InputIterator>()) > 0

This condition evaluates to true if the dereference operator is defined, and fails substitution if it is not defined.

Make a conjunction of all the conditions here and you should be set.

CodePudding user response:

The problem is that both 7 and 100 are int literals and so the templated overload is a better match than the nontemplate version because in the nontemplate version a conversion is required from int to size_type for the first argument while in the template version there is no conversion required as InputIterator is deduced to be int.

One possible way of solving this is by adding suffix u after the first argument 7 or by casting first argument to size_type.

Below is a contrived example:

template<typename InputIterator>
void assign(InputIterator first, InputIterator last)  //#1
{
    std::cout<<"template version"<<std::endl;
}
void assign(std::size_t n, const int& val)           //#2
{
    std::cout<<"nontemplate versino"<<std::endl;
}
int main()
{
    assign(7,100);                                  //calls #1 as #1 is a better match
    assign(7u, 100);                                //calls #2
//          ^------------->added u
    return 0;
}
  • Related