Stringify-ing Все
Я думаю, было бы хорошо, если бы вы увидели пример того, как это будет работать на практике.Строковая индикация должна быть быстрой , легкой и простой .К сожалению, он не встроен в стандартную библиотеку.
Тем не менее, в чуть более 100 строках кода мы можем написать код для строкового преобразования std::map
, std::tuple
, std::unordered_map
, std::set
, std::unordered_set
, std::list
, std::vector
, std::pair
, std::tuple
, и любая их комбинация. Так, например, у вас может быть кортеж, содержащий вектор unordered_maps между int и строками, среди прочих misc.Вещи:
std::vector<std::unordered_map<int, std::string>> vect {
{{10, "Hello"}, {20, "World"}},
{{1, "1"}, {2, "2"}, {3, "3"}},
{{1000, "thousand"}, {1000000, "million"}},
};
// If we can print this, we're GOLDEN
auto justTryAndPrintMe = std::tuple{10, 20, vect, 0.3f, short(3), 2398987239890ll};
Благодаря своему дизайну интерфейс, который мы напишем, будет легко распространяться на пользовательские типы .
Описание интерфейса
Интерфейс состоит из трех частей.
to_string
перегрузки (это обеспечивается std::to_string
) append_to
перегрузки (мы пишем их) - Один
stringify
шаблон функции,это звонит append_to
.
Давайте сначала рассмотрим функцию stringify
:
namespace txt
{
template<class T>
std::string stringify(T const& t)
{
std::string s;
append_to(s, t);
return s;
}
}
Зачем использовать append_to
?Присоединение к строке очень быстро.Когда вы добавляете несколько раз, std::string
резервирует больше емкости, чем фактически необходимо, поэтому маловероятно, что любое отдельное добавление приведет к новому выделению, поскольку строка будет иметь достаточную емкость, чтобы соответствовать объекту.Это уменьшает количество звонков до new
и delete
.
Написание интерфейса.
append_to
имеет запасной вариант, который использует to_string
, а также ряд специализаций для различных контейнеров.Специализации будут объявлены заранее, чтобы они были видны друг другу.
Откат.
Эта версия наименее специализированная, и она доступна только в том случае, если нет другого способа добавить объект.Мы используем SFINAE для проверки существования метода to_string
.
Этот to_string
метод может происходить из пространства имен std;он может быть определен в глобальном пространстве имен, или to_string
может быть даже определен в том же пространстве имен, что и пользовательский тип. В последнем случае компилятор находит его через ADL (Argument Dependent Lookup),
namespace txt {
using std::to_string;
// This nasty bit in the template declaration is the SFINAE
template<class T, class = decltype(to_string(std::declval<T>()))>
void append_to(std::string& s, T const& obj)
{
s += to_string(obj);
}
}
Особые случаи
Есть функция to_string
для std::string
или cstrings, но для них просто написать эффективные функции добавления.
// namespace txt
template<size_t N>
void append_to(std::string& s, char const(&str)[N]) {
s.append(str, N - 1);
}
void append_to(std::string& s, std::string const& s2) {
s += s2;
}
Форвард-декларации контейнеров и типов STL
Хорошая идея - форвард-декларировать их, так как это позволит им вызывать друг друга.
// namespace txt
template<class... T>
void append_to(std::string& s, std::tuple<T...> const& t);
template<class F, class S>
void append_to(std::string& s, std::pair<F, S> const& p);
template<class T>
void append_to(std::string& s, std::vector<T> const& v);
template<class T>
void append_to(std::string& s, std::set<T> const& v);
template<class T>
void append_to(std::string& s, std::list<T> const& v);
template<class Key, class Value>
void append_to(std::string& s, std::map<Key, Value> const& v);
template<class T>
void append_to(std::string& s, std::unordered_set<T> const& v);
template<class Key, class Value>
void append_to(std::string& s, std::unordered_map<Key, Value> const& v);
Диапазоны обработки
Это довольно просто.Мы напишем append_range
функцию, которая принимает начальный и конечный итератор, и мы ll use the
append_range function to implement
append_to` для контейнеров STL.
// namespace txt
template<class It>
void append_range(std::string& s, It begin, It end) {
if(begin == end) {
s += "{}";
} else {
s += '{';
append_to(s, *begin);
++begin;
for(; begin != end; ++begin) {
s += ", ";
append_to(s, *begin);
}
s += '}';
}
}
Обработка пар и кортежей
Мы можем использовать std::apply
(введено в C ++ 17) для расширения кортежа в пакет параметров.Получив это, мы можем использовать выражение сгиба для добавления каждого из элементов.
// namespace txt
template<class... T>
void append_to(std::string& s, std::tuple<T...> const& t)
{
if constexpr(sizeof...(T) == 0)
{
s += "{}";
}
else
{
// This is a lambda. We'll use it to append the tuple elements.
auto append_inputs = [&s](auto& first, auto&... rest)
{
// Append the first one
append_to(s, first);
// Append the rest, separated by commas
((s += ", ", append_to(s, rest)) , ...);
};
s += '(';
std::apply(append_inputs, t);
s += ')';
}
}
Добавление пары также довольно просто:
// namespace txt
template<class F, class S>
void append_to(std::string& s, std::pair<F, S> const& p)
{
s += '(';
append_to(s, p.first);
s += ", ";
append_to(s, p.second);
s += ')';
}
Обработка контейнеров STL
Для всего этого мы просто вызываем append_range
наначальный и конечный итераторы.
template<class T>
void append_to(std::string& s, std::vector<T> const& v) {
append_range(s, v.data(), v.data() + v.size());
}
template<class T>
void append_to(std::string& s, std::set<T> const& v) {
append_range(s, v.begin(), v.end());
}
template<class Key, class Value>
void append_to(std::string& s, std::map<Key, Value> const& v) {
append_range(s, v.begin(), v.end());
}
template<class T>
void append_to(std::string& s, std::unordered_set<T> const& v) {
append_range(s, v.begin(), v.end());
}
template<class Key, class Value>
void append_to(std::string& s, std::unordered_map<Key, Value> const& v) {
append_range(s, v.begin(), v.end());
}
Пользовательские типы: так же просто, как написание функции
Это действительно просто.Мы можем просто написать метод append_to
для типа.Это может пойти после того, как вы включите заголовочный файл, содержащий namespace txt
.ADL находит это автоматически.
namespace foo {
struct MyVec {
int x;
int y;
int z;
};
void append_to(std::string& s, MyVec v) {
using txt::append_to;
s += '<';
append_to(s, v.x);
s += ", ";
append_to(s, v.y);
s += ", ";
append_to(s, v.z);
s += '>';
}
}
Используя этот код в вашей библиотеке
Мы можем написать функцию append_to
для Table
довольно легко!
namespace YourNamespace {
// Here, I forward declare append_to for table
// Forward-declaring it allows you to have tables of tables
template <class Key, class Value>
void append_to(std::string& s, Table<Key, Value> const& table);
template <class Key, class Value>
void append_to(std::string& s, ListNode<Key, Value> const& node)
{
// We need to use the txt version so it's included in our overload set
using txt::append_to;
append_to(s, node.key);
s += " : ";
append_to(s, node.value);
}
template <class Key, class Value>
void append_to(std::string& s, Table<Key, Value> const& table)
{
using txt::append_to;
ListNode<Key, Value> const* current = table.table;
if (current == nullptr)
{
s += "{}";
}
else
{
s += "{ ";
append_to(s, *current);
current = current->next;
for (; current != nullptr; current = current->next) {
s += ", ";
append_to(s, *current);
}
s += " }";
}
}
} // namespace YourNamespace