@rubenvb's answer is great.
As an alternative
bool get_line_into_vector( std::istream& is, std::vector<std::string>& v ) {
  std::string tmp;
  if (!std::getline(is, tmp))
     return false;
  v.push_back(std::move(tmp));
  return true;
}
std::vector<std::string> lines;
while(get_line_into_vector( ifs, lines ))
{} // do nothing
This is rubenvb's solution with the temporary moved into a helper function.
We can avoid the small buffer optimization sized copies of characters with this:
bool get_line_into_vector( std::istream& is, std::vector<std::string>& v ) {
  v.emplace_back();
  if (std::getline(is, v.back()))
    return true;
  v.pop_back();
  return false;
}
this can (in an edge case) cause an extra massive reallocation, but that is asymptotically rare.
Unlike @pschill's answer, here the invalid states are isolated to within a helper function, and all the flow control is centered around avoiding those invalid states from leaking.
The nice thing is that
std::vector<std::string> lines;
while(get_line_into_vector( ifs, lines ))
{} // do nothing
is how you use it; which of these two implementations you use is isolated to within the get_line_into_vector function.  That lets you swap between them and determine which is better.