Home > Enterprise >  Return Type Resolver and ambiguous overload for 'operator='
Return Type Resolver and ambiguous overload for 'operator='

Time:06-16

I've copied code from this wiki and it works.

The problem occurs when I make this code:

int main()
{
  std::set<int> random_s = getRandomN(10);
  std::vector<int> random_v;
  random_v = getRandomN(10);
  std::list<int> random_l = getRandomN(10);
}

My compiler (gcc trunk) prints out this error:

error: ambiguous overload for 'operator=' (operand types are 'std::vector<int, std::allocator<int> >' and 'getRandomN')
   43 |   random_v = getRandomN(10);

I don't understand why the C compiler can't simply take copy operator= but try to match operator=(initializer_list<value_type> __l) and operator=(vector&& __x) instead.

Here is my solution to this problem, which I don't like, but I can't think of anything else:

  1. The functional cast:

    random_v = std::vector<int>(getRandomN(10));
    

    Clearly, the type must be repeated twice.

  2. Private inheritance and forwarding required methods from the parent:

    template<typename T>
    class my_vector : private std::vector<T>
    {
    public:
        using std::vector<T>::end;
        using std::vector<T>::insert;
    
        my_vector<int>& operator=(my_vector<int> rhs) noexcept
        {
            std::swap(*this, rhs);
            return *this;
        }
    };
    
    my_vector<int> random_v1;
    random_v1 = getRandomN(10);
    

    Clearly, I don't use std::vector<int> anymore...

The whole code: godbolt

CodePudding user response:

The issue is that, from the conversion operator's signature alone, the compiler can't tell if it should convert getRandomN(10) to a std::initializer_list<int> and then assign that to random_v or convert getRandomN(10) to a std::vector<int> and then assign that to random_v. Both involve exactly one user-defined conversion, and so neither is a better choice from the compiler's point of view.

Of course, once you look at the body of the conversion operator it becomes clear that std::initializer_list<int> won't work, since it has no insert member function, but that's too late. The compiler makes its choice for overload resolution before looking at the body of the function.

The way to make this work is to make it clear that std::initializer_list<int> isn't the right choice from the signature alone. If you have access to C 20 concepts that's pretty easy:

template <typename T>
concept BackInsertable = requires(T t) { t.insert(std::end(t), 0); };

class getRandomN 
{
  size_t count;

public:
  getRandomN(int n = 1) : count(n) {}

  // ------ vvvvvvvvvvvvvv ------ NOTE HERE
  template <BackInsertable Container>
  operator Container () {
    Container c;
    for(size_t i = 0;i < count;   i)
      c.insert(c.end(), rand()); // push_back is not supported by all standard containers.
    return c;
  }
};

Without concepts you'll need to use other SFINAE tricks to make that operator invalid. Here's one possible implementation that works all the way back to C 11:

template <typename T>
using BackInsertable = decltype(std::declval<T&>().insert(std::end(std::declval<T&>()), 0));

class getRandomN 
{
  size_t count;

public:
  getRandomN(int n = 1) : count(n) {}

  template <typename Container, BackInsertable<Container>* = nullptr>
  operator Container () {
    Container c;
    for(size_t i = 0;i < count;   i)
      c.insert(c.end(), rand()); // push_back is not supported by all standard containers.
    return c;
  }
};
  • Related