简体   繁体   中英

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

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.

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

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, 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM