简体   繁体   中英

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

I have a function which I have an overload for like such:

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

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

And I call it like:

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

I would expect this to resolve to the second overload, however this is apparently ambiguous. To my surprise I found this to compile:

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

Where the variable only contains 'hello' which doesn't seem very useful nor intuitive.

With this I have two questions:

  1. What is causing this to be a valid initialiser for an std::string ?
  2. Can this be worked around in some way which does not involve renaming the function or avoiding initialiser lists?

std::string has a constructor:

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

The call

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

resolves to that constructor. Even though syntactically the call resolves to a valid constructor, this will lead to undefined behavior at run time since "hello" and "hey" are unrelated strings.

The only way to make

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

to resolve to the second function is to make it explicit.

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

I'd back up. Instead of fixing your problem, I'd expose a process( std::experimental::span<const std::string> ) overload.

See std::experimental::array_view , a weaker version of which is easy to write.

Most of the work is in the long list of ctors you want:

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()) {}
};

This gets rid of needless allocation in your vector implementation.

Which makes the single-string version pointless.

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

to call with a string, do a process({"bob"}) . For a string s , do process({s}) . To call with a vector v, do process(v) .

An industrial quality span would only have the process(non_const_T) style constructors for a const T , but I'm lazy.

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