如何正确解决 const std::string& 和 const std::vector<string>& 的歧义?

How to properly solve const std::string& and const std::vector<string>& ambiguity?

我有一个函数,我有一个像这样的重载:

void process(const std::string& text)
{
}

void process(const std::vector<std::string>& manyTexts)
{
}

我这样称呼它:

process({"hello", "hey"});

我希望这可以解决第二个过载,但这显然是模棱两可的。令我惊讶的是,我发现这个可以编译:

std::string text = {"hello", "hey"};

其中变量仅包含 'hello',这似乎不是很有用也不直观。

有了这个我有两个问题:

  1. 是什么导致它成为 std::string 的有效初始化程序?
  2. 是否可以通过某种方式解决这个问题而不涉及重命名函数或避免初始化列表?

std::string 有一个构造函数:

template< class InputIt >
basic_string( InputIt first, InputIt last, 
              const Allocator& alloc = Allocator() );

来电

std::string text = {"hello", "hey"};

解析为该构造函数。即使在语法上调用解析为有效的构造函数,这也会导致在 运行 时出现未定义的行为,因为 "hello""hey" 是不相关的字符串。

唯一的方法是

process({"hello", "hey"});

解析到第二个函数就是让它显式化。

process(std::vector<std::string>{"hello", "hey"});

我会支持。我没有解决你的问题,而是公开了一个 process( std::experimental::span<const std::string> ) 过载。

参见std::experimental::array_view,弱化的版本容易写。

大部分工作都在您想要的一长串 ctors 中:

template<class T>
struct span {
  T* b = nullptr;
  T* e = nullptr;
  T* begin() const { return b; }
  T* end() const { return e; }
  bool empty() const { return begin()==end(); }
  std::size_t size() const { return end()-begin(); }
  T& front() const { return *begin(); }
  T& back() const { return *std::prev(end()); }

  // extra useful things I have added in my version:
  span without_front(std::size_t N=1) const {
    return {begin()+(std::min)(size(), N), end()};
  }
  span without_back(std::size_t N=1) const {
    return {begin(), end()-(std::min)(size(), N)};
  }

  // ctors: span uses "pointer semantics":
  span()=default;
  span(span const&)=default;
  span& operator=(span const&)=default;
  ~span()=default;
  // useful ctors for making a span:
  span( T* s, T* f ):b(s), e(f) {}
  span( T* s, std::size_t len ):span(s, s+len) {}

  using non_const_T = std::remove_const_t<T>;

  template<class A>
  span( std::vector<T, A>& in ):span(in.data(), in.size()) {}
  template<class A>
  span( std::vector<non_const_T, A> const& in ):span(in.data(), in.size()) {}
  template<class Traits>
  span( std::string<T, Traits>& in ):span(in.data(), in.size()) {}
  template<class Traits>
  span( std::string<non_const_T, Traits> const& in ):span(in.data(), in.size()) {}
  template<std::size_t N>
  span( std::array<T, N>& in ):span(in.data(), in.size()) {}
  template<std::size_t N>
  span( std::array<non_const_T, N>const& in ):span(in.data(), in.size()) {}
  template<std::size_t N>
  span( T(&in)[N] ):span(in.data(), in.size()) {}
  span( std::initializer_list<non_const_T> in ):span(in.begin(), in.size()) {}
};

这消除了 vector 实施中不必要的分配。

这使得单字符串版本毫无意义。

void process(span<const std::string> texts)
{
   for (const std::string& text:texts) {
     // process one element
   }
}

要用字符串调用,请执行 process({"bob"})。对于字符串 s,执行 process({s})。要使用向量 v 调用,请执行 process(v).

工业品质 span 只有 process(non_const_T) 风格的构造函数 const T,但我很懒。