2012-11-07 3 views
5

다음 함수에서 문자열 sT 유형을 읽을 수 있는지 여부와 입력이 완전히 소모 된 경우 T 유형으로 변환 할 수 있는지 확인하려고합니다. 나는 i.eof() 함수의 끝에 거짓이기 때문에 can_be_converted_to<bool>("true")는 false로 평가, 그러나 스트림 추출이 모든 입력을 소비했는지 어떻게 확인합니까?

template <class T> 
bool can_be_converted_to(const std::string& s, T& t) 
{ 
    std::istringstream i(s); 
    i>>std::boolalpha; 
    i>>t; 
    if (i and i.eof()) 
    return true; 
    else 
    return false; 
} 

를 원한다.

문자열의 끝에이 지난 을 읽으려고 시도하지 않았기 때문에 함수가 전체 문자열을 읽었음에도 불구하고 이것은 정확합니다. (다음을 읽을 때 istringstream이 끝을지나 읽기 때문에 그래서, 분명히이 기능은 INT 두 번 작동합니다.) 그래서

, 내가 참으로 (i and <input completely consumed>)을 확인해야한다고 가정 :

Q를 : 어떻게 입력이 있음을 확인합니까 eof()를 사용하지 않고 완전히 소비 되었습니까?

+0

답이 아닙니다. 단지 메모 만 들어 있습니다 : 't'형식의 tmp 변수를 사용하는 것이 좋습니다. case'e.eof()'는 거짓이다. – Mene

+0

'i.peek()'를하면'EOF'가 리턴됩니까? –

답변

8

사용 peek() 또는 get() 스트림에서의 향후 계획에 대해 확인 :

return (i >> std::boolalpha >> t && i.peek() == EOF); 

버전이 중, 정수 작동하지 않습니다. 이 입력을 고려하십시오 : 123 45. 스트림에 아직 남아있는 문자가 있더라도 123을 읽고 사실을보고합니다.

3

표준 라이브러리의 많은 구현에서 eof은 끝을 넘어 읽기를 시도한 후에 만 ​​설정됩니다. 당신은 수행하여 코드에서 그것을 확인할 수 있습니다

char _; 
if (i && !(i >> _)) { // i is in a valid state, but 
         // reading a single extra char fails 
3

제록의 대답에 확장, 당신은 적어도이 경우, 그냥 간단하게 i.peek()i.get()를 사용할 수 있습니다. (다른 하나를 선호 할 이유 가 있는지 모르겠어요.)

을 또한, 공백, 당신은 검사 전에 추출 할 수있는 분리 아무것도하지만 결코 없다는 것을 컨벤션 다음 종료. 같은 뭔가 : std::ws

return i >> std::ws && i.get() == std::istream::traits_type::eof(); 

일부 구형 구현은 버그가 있었고, 오류 상태에 스트림을 둘 것입니다.

return !(i >> std::ws) || i.get() == std::istream::traits_type::eof(); 

하거나 조건 전에 std::ws을 읽고, i.get()에 유일하게 의존 :이 경우, 당신은 테스트를 반전, 과 같은 것을해야 할 것입니다.

(버그 std::ws 여전히 문제의 경우 모르겠다는. 나는 때 다시 일을 그것의 버전을 개발하고, 난 그냥 그것을 사용 에 계속했습니다.)

2

나는 것 완전히 다른 접근 방식을 제공하는 것 : 입력 문자열을 가져 와서 직접 tokenize 한 다음 boost::lexical_cast<T>을 사용하여 개별 필드를 변환하십시오.

이유 : 나는 2 개의 int 및 2 개의 double 필드가 포함 된 문자열을 공백으로 구분하여 구문 분석하는 데 오후를 낭비했습니다.

int i, j; 
double x, y; 
std::istringstream ins{str}; 

ins >> i >> j >> x >> y; 
// how to check errors???... 

제대로
`"5 3 9.9e+01 5.5e+02"` 

등 정확한 입력을 구문 분석 만이 가진 문제를 감지하지 않습니다 :

`"5 9.6e+01 5.5e+02"` 

무엇 발생하는 i 설정 될 것입니다 다음을 수행 ~ 5 (OK)이면 j은 9 (??), x에서 6.0 (= 0.6e + 01), y에서 550 (OK)으로 설정됩니다. failbit이 설정되지 않은 것을보고 놀랐습니다. (플랫폼 정보 : OS X 10.9, Apple Clang ++ 6.0, C++ 11 모드).

당연히 이제는 "표준이 그렇게해야한다고 말하면서"라고 말하면 좋겠지 만 버그가 아니라 기능이라는 것을 알고 있으면 원하는 경우 고통을 줄일 수 있습니다 코드의 수천을 쓰지 않고도 적절한 오류 검사를 할 수 있습니다.

OTOH, "Marius"의 우수 tokeniser function을 사용하고 공백에 처음으로 str을 분할하면 갑자기 모든 것이 매우 쉽게됩니다. 다음은 토큰 처리기의 약간 수정 된 버전입니다. 문자열의 벡터를 반환하기 위해 다시 썼습니다. 원본은 문자열로 변환 가능한 요소가있는 컨테이너에 토큰을 넣는 템플릿입니다.

std::vector<std::string> tokens = tokenizer(str, " \t", true); 
if (tokens.size() < 4) 
    throw ParseError{"Too few fields in " + str}; 

try { 
    unsigned int i{ boost::lexical_cast<unsigned int>(tokens[0]) }, 
     j{ boost::lexical_cast<unsigned int>(tokens[1]) }; 
    double x{ boost::lexical_cast<double>(tokens[2]) }, 
     y{ boost::lexical_cast<double>(tokens[3]) }; 
    // print or process i, j, x, y ... 
} catch(const boost::bad_lexical_cast& error) { 
    throw ParseError{"Could not parse " + str}; 
} 

주 : (일부 예외 객체가 ParseError)

// \param str: the input string to be tokenized 
// \param delimiters: string of delimiter characters 
// \param trimEmpty: if true then empty tokens will be trimmed 
// \return a vector of strings containing the tokens 
std::vector<std::string> tokenizer(
    const std::string& str, 
    const std::string& delimiters = " ", 
    const bool trimEmpty = false 
) { 
    std::vector<std::string> tokens; 
    std::string::size_type pos, lastPos = 0; 
    const char* strdata = str.data(); 
    while(true) { 
     pos = str.find_first_of(delimiters, lastPos); 
     if(pos == std::string::npos) { 
      // no more delimiters 
      pos = str.length(); 
      if(pos != lastPos || !trimEmpty) { 
       tokens.emplace_back(strdata + lastPos, pos - lastPos); 
      } 
      break; 
     } else { 
      if(pos != lastPos || !trimEmpty) { 
       tokens.emplace_back(strdata + lastPos, pos - lastPos); 
      } 
     } 
     lastPos = pos + 1; 
    } 
    return tokens; 
} 

을 (. 이러한 일반적인 접근 방식을 필요로하는 사람들을 위해 위의 원래의 링크를 참조하시기 바랍니다) 후 바로 다음과 같이 사용하실 수 있습니다 원한다면 Boost split 또는 tokenizer을 사용하십시오.하지만 적어도 제 환경에서는 Marius의 토큰 화가보다 느립니다. 대신 boost::lexical_cast<T> 대신 "std::sto*"기능 (예 : stoi)을 사용하여 문자열 토큰을 int로 변환 할 수 있습니다. 이들은 변환을 수행 할 수없는 경우 std::invalid_argument과 변환 된 값을 표현할 수없는 경우 std::out_of_range의 두 가지 예외를 throw합니다. 별도로 또는 부모 std::runtime_error을 잡을 수도 있습니다. 위의 예제 코드를 수정하면 독자에게 연습 문제로 남아 있습니다 :-)

관련 문제