Каков предпочтительный способ представления структур данных в виде строк в C ++? - PullRequest
0 голосов
/ 05 июня 2019

Каков предпочтительный способ представления структуры данных в C ++? Я хотел бы использовать строки, хотя, если есть лучший вариант, я бы хотел попробовать это.

Я создавал структуру данных для использования в более крупной программе. Структура должна представлять что-то похожее на словарь Python. Я хочу напечатать всю структуру данных в виде строки. Есть ли стандартный способ сделать это в C ++? В руководящих указаниях предлагается использовать функцию to_string(), однако я не уверен, что она работает так, как я хочу.

Я использую шаблонные классы и не уверен, что существует одноразовое решение для обработки любой комбинации ключей / значений, которые выбрасываются в программу.

Я тестировал это до сих пор с char.

// Table class
std::string toString()
  {
    std::string result = " { ";
    ListNode<Key, Value>* current = this->table;
    while(current != nullptr)
    {
      result += current->toString();
      current = current->next;
    }
    result += " } ";
    return result;
  }

// List Class
std::string toString()
  {
    std::string result = "";
    result += std::to_string(this->key);
    result += " : ";

    Node<Value>* current = this->list;

    while(current != nullptr) // changed to be current, not list
    {
      result += std::to_string(current->value);
      current = current->next;
    }

    return result; // added return
  }

Эта функция больше похожа на компоновщик для конкретной строки. Это входит в меньшие структуры данных, вызывающие подобные функции, чтобы добраться до базы.

Я ожидал что-то вроде:

{Ключ1: значение1, значение2, значение3, Ключ2: значение1, Ключ3: значение1, значение2}

Я только что получил {}

1 Ответ

0 голосов
/ 05 июня 2019

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
...