shavit
shavit

Reputation: 1063

Stream extracting from std::stringstream with spaces

I have this template function that gets an element from an std::map as a string, converts it to generic T through std::stringstream operator>> and returns it.

However the extraction operator only extracts the first word in the stream.

I have tried .str(), but for a template function it won't do. I have also tried ss >> std::noskipws >> result; but it does not seem to return valid data.

template<typename T>
T CConfig::get(const char *setting)
{
    std::stringstream ss;
    ss << this->m_settings[setting];
    // this->m_settings[setting] = "this is a test"

    T result;
    ss >> result;
    // result = "this"

    return result;
}

My expectation is that if m_settings[setting] is "this is a test", the same string will be returned in my get function. And at the same time, I don't want to break the template by hardcoding ss.str() and using a different function for strings.

Upvotes: 1

Views: 965

Answers (2)

David G
David G

Reputation: 96800

Here are 3 ways:

  1. Like I said in my comment, you can specialize the function get<std::string> and call std::getline() from there.

    template<typename T>
    T CConfig::get(const char* settings) {
      std::istringstream stream(m_settings[settings]);
      T t; assert(stream >> t);
      return t;
    }
    
    template<>
    std::string CConfig::get<std::string>(const char* settings) {
      std::istringstream stream(m_settings[settings]);
      T t; assert((std::getline(stream, t)));
      return t;
    }
    

    The cons of this approach is that it causes code-duplication.

  2. Delegate to a helper function that uses a primary and specialized overload.

    template<typename T>
    T CConfig::get(const char* settings) {
      T t;
      assert(
        do_get(std::istringstream(m_settings[settings]) >> std::skipws, t);
      );
      return t;
    }
    
    template<typename T>
    bool do_get(std::istringstream& stream, T& data) {
      return stream >> data;
    }
    
    template<>
    bool do_get(std::istringstream& stream, std::string& data) {
      return std::getline(stream, data);
    }
    
  3. If you have then just use if constexpr:

    template<class T>
    T CConfig::get(const char* settings) {
      T t;
      std::istringstream stream(m_settings[settings]);
      if constexpr(std::is_same_v<T, std::string>) {
        assert((std::getline(stream, t)));
      } else {
        assert(stream >> t);
      }
      return t;
    }
    

Upvotes: 1

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385108

You have to decide exactly what you want the behaviour to be for strings. If you want to extract a whole line, you can use std::getline(). Or in this case, perhaps just return this->m_settings[setting];.

Whatever you end up doing, the solution is to put the variant behaviour into a specialisation for get<std::string>.

Just be careful that the specialised behaviour doesn't deviate too far from that of the primary code, because that might be confusing to your users (which includes yourself!).

Upvotes: 3

Related Questions