Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use iterators for split #14

Merged
merged 2 commits into from
Jun 13, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions include/rcpputils/split.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,34 +36,57 @@
#ifndef RCPPUTILS__SPLIT_HPP_
#define RCPPUTILS__SPLIT_HPP_

#include <iterator>
#include <sstream>
#include <string>
#include <vector>

namespace rcpputils
{

/// Split a specified input into tokens using a delimiter.
/// Split a specified input into tokens using a delimiter and a type erased insert iterator.
/**
* The returned vector will contain the tokens split from the input
*
* \param[in] input the input string to be split
* \param[in] delim the dlelimiter used to split the input string
* \return A vector of tokens.
* \param[in] insert iterator pointing to a storage contianer
*/
inline std::vector<std::string>
split(const std::string & input, char delim, bool skip_empty = false)
template<
class InsertIterator,
typename std::enable_if<

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could static_assert be used here instead to improve readability?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I opted for SFINAE is that static_assert doesn't participate in function overloading. That being, the current template approach has the benefit of having another function declaration set with std::enable_if which accepts a different form of iterator. One of them could be just a class member function, e.g. add which takes the string and operates on it.
Does this make sense?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, that makes sense. I just feel it's hard to read and I wished there was a better way to write this, but I don't see any right now so this LGTM ;)

std::is_same<
InsertIterator &,
decltype(std::declval<InsertIterator>().operator=(std::declval<std::string>()))>::value
>::type * = nullptr>
inline void
Karsten1987 marked this conversation as resolved.
Show resolved Hide resolved
split(const std::string & input, char delim, InsertIterator & it, bool skip_empty = false)
{
std::vector<std::string> result;
std::stringstream ss;
ss.str(input);
std::string item;
while (std::getline(ss, item, delim)) {
if (skip_empty && item == "") {
continue;
}
result.push_back(item);
it = item;
}
}

/// Split a specified input into tokens using a delimiter.
/**
* The returned vector will contain the tokens split from the input
*
* \param[in] input the input string to be split
* \param[in] delim the dlelimiter used to split the input string
* \return A vector of tokens.
*/
inline std::vector<std::string>
split(const std::string & input, char delim, bool skip_empty = false)
{
std::vector<std::string> result;
auto it = std::back_inserter(result);
split(input, delim, it, skip_empty);
return result;
}
} // namespace rcpputils
Expand Down
77 changes: 77 additions & 0 deletions test/test_split.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@

#include <gtest/gtest.h>

#include <exception>
#include <list>
#include <set>
#include <string>
#include <tuple>
#include <vector>

#include "rcpputils/split.hpp"

TEST(test_split, split) {
Expand Down Expand Up @@ -221,3 +228,73 @@ TEST(test_split, split_backslash) {
EXPECT_EQ("world", ret[5]);
}
}

TEST(test_split, iterator)
Karsten1987 marked this conversation as resolved.
Show resolved Hide resolved
{
std::string s = "my/hello/world";

std::vector<std::string> vec = {};
auto vec_it = std::back_inserter(vec);
rcpputils::split(s, '/', vec_it);
EXPECT_EQ("my", vec[0]);
EXPECT_EQ("hello", vec[1]);
EXPECT_EQ("world", vec[2]);

std::list<std::string> ll = {};
auto ll_it = std::back_inserter(ll);
rcpputils::split(s, '/', ll_it);
EXPECT_EQ("my", ll.front());
EXPECT_EQ("world", ll.back());

s = "wonderful string with wonderful duplicates duplicates";
std::set<std::string> set = {};
auto set_it = std::inserter(set, std::begin(set));
rcpputils::split(s, ' ', set_it);
EXPECT_EQ(4u, set.size());
}

class TripleExtractor
{
size_t counter = 0;

public:
std::string package_name;
std::string middle_element;
std::string message_name;

TripleExtractor & operator=(const std::string & s)
{
if (counter >= 3) {
throw std::out_of_range("triple extractor only can hold three parts");
}

if (counter == 0) {
package_name = s;
} else if (counter == 1) {
middle_element = s;
} else {
message_name = s;
}

counter++;

return *this;
}

std::tuple<std::string, std::string, std::string> get() const
{
return std::tie(package_name, middle_element, message_name);
}
};

TEST(test_split, custom_iterator)
Karsten1987 marked this conversation as resolved.
Show resolved Hide resolved
{
std::string s = "my_pkg/msg/MyMessage";
TripleExtractor triple_it;
rcpputils::split(s, '/', triple_it);
EXPECT_STREQ("my_pkg", triple_it.package_name.c_str());
EXPECT_STREQ("msg", triple_it.middle_element.c_str());
EXPECT_STREQ("MyMessage", triple_it.message_name.c_str());

EXPECT_EQ(std::make_tuple("my_pkg", "msg", "MyMessage"), triple_it.get());
}