返回类型解析器和“operator=”的不明确重载

0 c++ oop inheritance stl vector

我从这个维基复制了代码并且它有效。

当我编写这段代码时出现问题:

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);
}
Run Code Online (Sandbox Code Playgroud)

我的编译器(gcc trunk)打印出以下错误:

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

我不明白为什么 C++ 编译器不能简单地采用copy operator=而是尝试匹配operator=(initializer_list<value_type> __l)and operator=(vector&& __x)

这是我对这个问题的解决方案,我不喜欢它,但我想不出其他的办法:

  1. 功能演员表:

    random_v = std::vector<int>(getRandomN(10));
    
    Run Code Online (Sandbox Code Playgroud)

    显然,该类型必须重复两次。

  2. 私有继承并从父级转发所需的方法:

    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);
    
    Run Code Online (Sandbox Code Playgroud)

    很明显,我不再使用了std::vector<int>...

完整代码:godbolt

Mil*_*nek 5

问题在于,仅从转换运算符的签名来看,编译器无法判断它是否应该转换getRandomN(10)为 astd::initializer_list<int>然后将其分配给random_v,或者转换getRandomN(10)为 astd::vector<int>然后将其分配给random_v。两者都只涉及一个用户定义的转换,因此从编译器的角度来看都不是更好的选择。

当然,一旦您查看转换运算符的主体,就会发现它std::initializer_list<int>不起作用,因为它没有insert成员函数,但为时已晚。编译器在查看函数体之前选择重载决策。

实现这一目标的方法是明确表明std::initializer_list<int>仅从签名来看这并不是正确的选择。如果您能够了解 C++20 概念,那么这非常简单:

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;
  }
};
Run Code Online (Sandbox Code Playgroud)

如果没有概念,您将需要使用其他 SFINAE 技巧来使该运算符无效。下面是一种可以一直追溯到 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;
  }
};
Run Code Online (Sandbox Code Playgroud)