Використання автоматичних параметрів шаблонів в C++17

В C++17 додали auto параметри шаблонів. Це значно спростило використання шаблонів. У якості аргументів можна писати, що прийдеться, а компілятор застосовує усі свої дедуктивні методи, щоб з тим розібратися. У цій статті ми розглянемо, як використовувати auto параметри варіативних шаблонів (variadic templates) на практичному прикладі — серіалізації об’єкту в JSON.

Увага: для компіляції прикладів не забувайте задати опцію —std=c++17 у вашому gcc-8 компіляторі :)

Параметри

Припустимо, що у нас є такий простий клас даних Pdo:

struct Pdo {
    bool   b;
    int    i;
    string s;
} pdo;

чиї екземпляри нам потрібно писати у форматі JSON. Мова C++ поки що не має механізмів для ітерації по усіх полях класу, тож для стартової позиції нам необхідно власноруч перелічити усі поля як параметри якогось варіативного шаблону. Хай то буде Object, як в JavaScript:

Object<&Pdo::b, &Pdo::i, &Pdo::s>

Щоб серіалізувати дані, необхідно знати, куди писати, що писати, як писати і які імена дати властивостям в тексті JSON. Куди писати і що писати — нехай це будуть параметри якогось методу, наприклад write:

Object<&Pdo::b, &Pdo::i, &Pdo::s>::write(pdo, cout);

Імена властивостей здобудемо з __PRETTY_FUNCTION__ (про це див. далі), а як писати — будемо визначати за типом поля. З цими рішеннями фронтальний шаблон отримає такий вигляд:

template<class Class, auto ... Members>
struct Object {
    ostream& write(Class&, ostream&);
};

Тут, щоб трохи спростити написання та використання шаблону, ми додали першим параметром клас, з яким працюємо, хоча, якщо є бажання, його можна віднайти за першим параметром шаблону.

Щоб використовувати auto, слід задати правила дедукції, а це значно простіше робити по кожному параметру окремо, ніж по усіх разом. Для цього додаймо новий шаблон Property. А щоб працювати з усіма Property зі списку — ще один шаблон Properties:

template<auto Member>
struct Property;

template<class Class, typename ... List>
struct Properties;

Початковий пакунок параметрів auto ... Members перепакуємо в Property<Members> ... Таким чином, фронтальний шаблон набуває такого вигляду:

template<class Class, auto ... Members>
struct Object : Properties<Class, Property<Members> ...> {
    static ostream& write(const Class& obj, ostream& out) {
        out << "{";
        Properties<Class, Property<Members> ...>::write(obj, out);
        out << "}";
        return out;
    }
};

Стає зрозуміло, якою має бути реалізація write — запис фігурних дужок та усіх властивостей в порядку їх слідування. Для Properties напишемо простий ітератор по пакунку параметрів шаблону.

Спеціалізація для першого чи єдиного параметру:

template<class Class, typename First>
struct Properties<Class, First> {
    static inline ostream& write(const Class& obj, ostream& out) {
        return First::write(obj, out);
    }
};

Спеціалізація для усіх наступних параметрів:

template<class Class, typename First, typename ... List>
struct Properties<Class, First, List...> {
    static inline ostream& write(const Class& obj, ostream& out) {
        Properties<Class, First>::write(obj, out);
        out << ",";
        Properties<Class, List...>::write(obj, out);
        return out;
    }
};

Із нудними тривіальними речами покінчено, переходимо до цікавих.

Спеціалізації

Щоб компілятор міг застосувати дедукцію, необхідно надати спеціалізації шаблону. У нашому випадку дедукція починається з шаблону Property. Однак безпосередня спеціалізація від параметру auto не працює:

template<class Class, typename T>
struct Property<T Class::*Member> { }; //error: template argument 1 is invalid

Тож використаємо проміжний шаблон Value, параметром якого буде тип Type:

template<typename Type>
struct Value;

Загальна спеціалізація для полів будь-яких типів виглядатиме так:

template<class Class, typename T>
struct Value<T Class::*> {
    using class_type = Class;
    static inline ostream& write(const T& value, ostream& out) {
        return out << value;
    }
};

У цій спеціалізації шаблону ми говоримо, що параметр Type шаблону Value може бути вказівником на поле типу T у класі Class. Цей клас ми також зберігаємо у class_type — він нам знадобиться пізніше. А для запису значення поля використаємо звичний оператор operator<<. Для числових типів це дасть результат такий, як має бути. Проте для значення типу bool в JSON слід писати як true чи false, в той час як стандартний конвертер пише 1.
То ж зробимо окрему спеціалізацію для bool:

template<class Class>
struct Value<bool Class::*> {
    using class_type = Class;
    static inline ostream& write(bool value, ostream& out) {
        return out << (value ? "true" : "false");
    }
};

А стрічки (string) слід писати в лапках, тож зробимо спеціалізацію і для string:

template<class Class>
struct Value<string Class::*> {
    using class_type = Class;
    static inline ostream& write(const string& value, ostream& out) {
        out << quoted(value);
        return out;
    }
};

І для const char*:

template<class Class>
struct Value<const char* Class::*> {
    using class_type = Class;
    static inline ostream& write(const char* value, ostream& out) {
        out << quoted(value);
        return out;
    }
};

І для char* — тут ми просто перенаправимо на спеціалізацію для const char*:

template<class Class>
struct Value<char* Class::*> : Value<const char* Class::*> {};

Визначивши що є Value, повернемось до Property і напишемо його реалізацію з використанням Value:

template<auto Member>
struct Property : Value<decltype(Member)> {
    using Class = typename Value<decltype(Member)>::class_type;
    static inline ostream& write(const Class& obj, ostream& out) {
        out << quoted((const char*)nameof<Member>()) << "=";
        return Value<decltype(Member)>::write(obj.*Member,out);
    }
};

В той час як параметр Member шаблону Property — це вказівник на поле, в шаблон Value ми передаємо тип цього вказівника, який отримуємо за допомогою decltype(Member). Клас, до якого належить вказівник, ми отримуємо із шаблону Value, де ми його завбачливо зберегли в class_type. Використану тут функцію nameof<Member>(), що має повернути нам ім’я поля класу, зараз і напишемо. Для цього використаємо вбудовану змінну __PRETTY_FUNCTION__, яка в останніх версіях компілятора має кваліфікатор constexpr.

template<auto V>
constexpr string_view nameof() {
    constexpr string_view pretty = __PRETTY_FUNCTION__;
    return  pretty;
}

Така функція поверне нам стрічку з гарним ім’ям функції-шаблону nameof:

constexpr std::string_view nameof() [with auto V = &Pdo::i; std::string_view = std::basic_string_view<char>]

Якщо уважно придивитись, то вона містить те, що нам потрібно — &Pdo::i. Треба тільки знайти його у стрічці. Шукати будемо з кінця:

template<auto V>
constexpr string_view nameof() {
    constexpr string_view pretty = __PRETTY_FUNCTION__;
    constexpr auto end = pretty.rfind(';');
    constexpr auto begin = pretty.rfind(':',end) + 1;
    return  pretty.substr(begin, end - begin);
}

Цю функцію вже можна використовувати як є, однак слід пам’ятати, що бінарний файл буде містити всі екземпляри __PRETTY_FUNCTION__ в повну їх довжину. Щоб цього уникнути, слід зробити копію часу компіляції з потрібної частини стрічки. Для копіювання із string_view напишемо новий шаблон, який зберігатиме constexpr копію, зроблену посимвольно за допомогою index_sequence. Для його використання потрібно два конструктори: один прийматиме в параметри тільки стрічку, а інший, шаблонний, — стрічку і об’єкт індексатор. Рознесемо ці конструктори по різних класах: basic_cestring — допоміжний клас з індексатором та cestring — що його наслідує і має конструктор тільки зі стрічкою.

template<typename CharT, size_t Size>
struct basic_cestring {
    using value_type = CharT;
    /* конструктор посимвольного копіювання */
    template<size_t... I> constexpr
    basic_cestring(const char* str, index_sequence<I...>)
      : _data{str[I]...} {}
    inline constexpr operator const CharT* () const { return _data; }
    const CharT _data[Size + 1];
};
template<size_t Size>
struct cestring : public basic_cestring<char, Size>  {
    using index = make_index_sequence<Size>;
    constexpr cestring(const char* str)
    : basic_cestring<char, Size>(str, index{}) {}
};

Екземпляр індексатора index{}, який ми передаємо в basic_cestring, потрібний лише як засіб передачі типу пакунку параметрів size_t... I в шаблон конструктора basic_cestring.
З такими змінами функція nameof набуде вигляду:

template<auto V>
constexpr auto nameof() {
    constexpr string_view pretty = __PRETTY_FUNCTION__;
    constexpr auto end = pretty.rfind(']');
    constexpr auto begin = pretty.rfind(':',end) + 1;
    constexpr auto name = pretty.substr(begin, end - begin);
    constexpr auto length = name.length();
    constexpr cestring<length> result { name.data() };
    return result;
}

З тим, що ми вже написали, ми можемо писати JSON таким викликом:

Object<Pdo, &Pdo::b, &Pdo::i, &Pdo::s>::write(obj, cout);

Однак хотілося б просто cout << obj;. Для цього перегрузимо operator<<:

template<class Class>
inline ostream& operator<<(ostream& stream, const Class& object) {
    // і де ж тут взяти Object<Class, ...> ?
    return stream;
}

Щоб operator<< знав, як серіалізувати наш клас, потрібно асоціювати клас із його моделлю. Зупинимось на простому рішенні — вкладений тип з певним ім’ям. Наприклад так:

struct Pdo {
    bool   b;
    int    i;
    string s;
    using Json = Object<Pdo, &Pdo::b, &Pdo::i, &Pdo::s>;
};

Також у такій формі він перекриє всі інші шаблони операторів << з класами. Щоб цього не трапилось, використаємо шаблон оператора з додатковим параметром-запобіжником, для якого і використаємо вкладений тип Json. Тоді operator<< матиме вигляд:

template<class Class, class Json = typename Class::Json>
inline ostream& operator<<(ostream& stream, const Class& object) {
    return Json::write(object, stream);
}

Як винагороду ми отримуємо можливість писати вкладені об’єкти:

struct Cdo {
    Pdo pdo;
    double val;
    using Json = Object<Cdo, &Cdo::pdo, &Cdo::val>;
} cdo;

cout << cdo;

Вектори

Із типів даних JSON залишився не розглянутим масив — []. В межах цієї статті обмежимось лиш гомогенними масивами — векторами. Щоб мати змогу їх писати, спеціалізуємо шаблон Value для векторів:

template<class Class, typename T>
struct Value<vector<T> Class::*> {
    using class_type = Class;
    static inline ostream& write(const vector<T>& value, ostream& out) {
        const char* dlm = "";
        out << "[";
        for(auto & i : value) {
            out << dlm << i;
            dlm = ",";
        }
        return out << "]";
    }
};

Така спеціалізація відкрила нам можливість серіалізувати як вектори простих типів так і вектори об’єктів, за умови, що вони мають модель JSON визначену як вкладений тип Json.

struct Cdo {
    Pdo pdo;
    double val;
    vector<int> vect;
    vector<Pdo> array;
    using Json = Object<Cdo, &Cdo::pdo, &Cdo::val, &Cdo::vect, &Cdo::array>;
};

З тим, що ми з вами тут накодували, ми можемо писати об’єкт як JSON, проте JSON-текст — це не обов’язково об’єкт. Це може бути масив, стрічка чи навіть скалярна величина. Щоб підтримати тип значення масив, спеціалізуємо Value без Class:

template<typename T>
struct Value<vector<T> *> {
    static inline ostream& write(const vector<T>& value, ostream& out) {
        const char* dlm = "";
        out << "[";
        for(auto & i : value) {
            out << dlm << i;
            dlm = ",";
        }
        return out << "]";
    }
};

та для зручності використання створимо новий шаблон для інтерпретації вектора як JSON-масиву:

template<typename Vector>
struct Array {
    inline ostream& write(ostream& out) const {
        return Value<Vector>::write(vector_, out);
    }
    inline constexpr Array(const Vector& vector) : vector_(vector) {}
private:
    const Vector& vector_;
};

З таким шаблоном масив можна писати викликом методу write:

Array(v).write(cout)

А для того, щоб використати operator<<, доповнимо цей шаблон внутрішнім типом Json та статичним методом write:

template<typename Vector>
struct Array {
    using Json = Array<Vector>;
    inline ostream& write(ostream& out) const {
        return Value<Vector>::write(vector_, out);
    }
    static inline ostream& write(const Array<Vector>& vector, ostream& out) {
        return Value<Vector>::write(vector.vector_, out);
    }
    inline constexpr Array(const Vector& vector) : vector_(vector) {}
private:
    const Vector& vector_;
};

сout << Array(v);

Підсумок

Отже, витративши трішки часу на вивчення шаблонів з auto параметрами та написавши 160 рядків коду, ми створили можливість писати доволі складні JSON-об’єкти за допомогою простих конструкцій:

struct Pdo {
    bool   b;
    int    i;
    string s;
    using Json = Object<Pdo, &Pdo::b, &Pdo::i, &Pdo::s>;
};
struct Cdo {
    Pdo pdo;
    double val;
    vector<int> vect;
    vector<Pdo> array;
    using Json = Object<Cdo, &Cdo::pdo, &Cdo::val, &Cdo::vect, &Cdo::array>;
};

Cdo cdo { {false, 15, "Cdo"}, 1.2, {1, 2, 3, 5, 7}, {{true, 17, "cdo.pdo"}} };
cout << cdo;

Також ми розглянули, як робити спеціалізацію шаблонів з auto параметрами для дедукції, простий ітератор по пакунку параметрів, копії стрічок часу компіляції, використання index_sequence для ітерацій та трюк з __PRETTY_FUNCTION__ для отримання імен ідентифікаторів.

Додаток. Довершений код

Довершений код до цієї статті наведено нижче, а результат виконання можна подивитись тут.

#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
using namespace std;

template<typename CharT, size_t Size>
struct basic_cestring {
    using value_type = CharT;
    /* конструктор посимвольного копіювання */
    template<size_t... I> constexpr
    basic_cestring(const char* str, index_sequence<I...>)
      : _data{str[I]...} {}
    inline constexpr operator const CharT* () const { return _data; }
private:
    const CharT _data[Size + 1];
};

template<size_t Size>
struct cestring : public basic_cestring<char, Size>  {
    using index = make_index_sequence<Size>;
    constexpr cestring(const char* str)
    : basic_cestring<char, Size>(str, index{}) {}
};


template<auto V>
constexpr auto nameof() {
    constexpr string_view pretty = __PRETTY_FUNCTION__;
    constexpr auto end = pretty.rfind(']');
    constexpr auto begin = pretty.rfind(':',end) + 1;
    constexpr auto name = pretty.substr(begin, end - begin);
    constexpr auto length = name.length();
    constexpr cestring<length> result { name.data() };
    return result;
}


template<typename Type>
struct Value;

template<class Class, typename T>
struct Value<T Class::*> {
    using class_type = Class;
    static inline ostream& write(const T& value, ostream& out) {
        return out << value;
    }
};

template<class Class>
struct Value<bool Class::*> {
    using class_type = Class;
    static inline ostream& write(bool value, ostream& out) {
        return out << (value ? "true" : "false");
    }
};

template<class Class>
struct Value<string Class::*> {
    using class_type = Class;
    static inline ostream& write(const string& value, ostream& out) {
        out << quoted(value);
        return out;
    }
};

template<class Class>
struct Value<const char* Class::*> {
    using class_type = Class;
    static inline ostream& write(const char* value, ostream& out) {
        out << quoted(value);
        return out;
    }
};

template<class Class>
struct Value<char* Class::*> : Value<const char* Class::*> {};

template<typename T>
struct Value<vector<T>> {
    static inline ostream& write(const vector<T>& value, ostream& out) {
        const char* dlm = "";
        out << "[";
        for(auto & i : value) {
            out << dlm << i;
            dlm = ",";
        }
        return out << "]";
    }
};


template<class Class, typename T>
struct Value<vector<T> Class::*> {
    using class_type = Class;
    static inline ostream& write(const vector<T>& value, ostream& out) {
        const char* dlm = "";
        out << "[";
        for(auto & i : value) {
            out << dlm << i;
            dlm = ",";
        }
        return out << "]";
    }
};

template<auto Member>
struct Property : Value<decltype(Member)> {
    using Class = typename Value<decltype(Member)>::class_type;
    static inline ostream& write(const Class& obj, ostream& out) {
        out << quoted((const char*)nameof<Member>()) << "=";
        return Value<decltype(Member)>::write(obj.*Member,out);
    }
};

template<class Class, typename ... List>
struct Properties;

template<class Class, typename First>
struct Properties<Class, First> {
    static inline ostream& write(const Class& obj, ostream& out) {
        First::write(obj, out);
        return out;
    }
};

template<class Class, typename First, typename ... List>
struct Properties<Class, First, List...> {
    static inline ostream& write(const Class& obj, ostream& out) {
        Properties<Class, First>::write(obj, out);
        out << ",";
        Properties<Class, List...>::write(obj, out);
        return out;
    }
};

template<class Class, auto ... Members>
struct Object : Properties<Class, Property<Members> ...> {
    static ostream& write(const Class& obj, ostream& out) {
        out << "{";
        Properties<Class, Property<Members> ...>::write(obj, out);
        out << "}";
        return out;
    }
};

template<typename Vector>
struct Array {
    using Json = Array<Vector>;
    inline ostream& write(ostream& out) const {
        return Value<Vector>::write(vector_, out);
    }
    static inline ostream& write(const Array<Vector>& vector, ostream& out) {
        return Value<Vector>::write(vector.vector_, out);
    }
    inline constexpr Array(const Vector& vector) : vector_(vector) {}
private:
    const Vector& vector_;
};

template<class Class, class Json = typename Class::Json>
inline ostream& operator<<(ostream& stream, const Class& object) {
    return Json::write(object, stream);
}

//----------------------------------------------------------------------------

struct Pdo {
    bool   b;
    int    i;
    string s;
    using Json = Object<Pdo, &Pdo::b, &Pdo::i, &Pdo::s>;
};

struct Cdo {
    Pdo pdo;
    double val;
    vector<int> vect;
    vector<Pdo> array;
    using Json = Object<Cdo, &Cdo::pdo, &Cdo::val, &Cdo::vect, &Cdo::array>;
};


int main() {
    vector<unsigned> v { 13, 17 };
    Pdo obj { true, 11, "ss\"s" };
    Cdo cdo { {false, 15, "Cdo"}, 1.2, {1, 2, 3, 5, 7}, {{true, 17, "cdo.pdo"}} };

    cout << Array(v) << endl;
    cout << Array(cdo.array) << endl;
    Object<Pdo, &Pdo::b, &Pdo::i, &Pdo::s>::write(obj, cout) << endl;
    cout << obj << endl;
    cout << cdo << endl;
    return 0;
}
LinkedIn

78 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

Просмотрел мельком С++17 фичи, много чего интересного и полезного.
Проблема, как мне кажется в другом.

Если прыгать с проекта на проект (как часто происходит в аутсорс конторах).
То сегодня у тебя проект С++98, завтра С++11, потом 17й, потом опять 11й и т.д.
В итогое в голове каша получается что и где можно использовать, а что и где — нельзя.

То сегодня у тебя проект С++98, завтра С++11, потом 17й, потом опять 11й и т.д.

А что мешает сейчас отказываться от до-2011 вариантов?
Какой-то из монстров до сих пор не научился 11-му (особенно с учётом, что он на 90% вообще 2003)?

Часто в легаси бывает код, что компилируется только конкретной версией некоего компилятора, а фикс его дорог и не нужен в общем-то.
И не только легаси. TensorFlow жестко привязан к версиям gcc — это современный продукт, но его программисты сильно криворуки. Но им сейчас пользуются очень многие — пакет для питона неплох очень и его хорошо знают уже.

Да, на самом деле не научившихся 11-му «монстров» не так и мало. Просто потому что так привыкли, у них всё работает, больше ничего не нужно. Это их выбор.
Но и у соискателя тоже есть выбор не идти к таким. Благо вакансий хватает.

Откровенно говоря напоминает стрельбу по воробьям из реактивной артиллерии.

дык это же ж программирование и есть ))

что здесь воробьи, а что реактивная артиллерия?

Кстати вопрос знатокам.
С чем связан запрет на использование floating типов для non-type параметров шаблонов?

Припускаю (хай поправлять мене знавці) що це пов’язано із порівнянням величин — використовувати == для floating не найкраща практика

а есть в стд чтото типа qFuzzyCompare ?

Навіть якщо і є, то до компілятора це не матиме відношення. Крім того компілятор має бути детермінованим, бо інакше один і той самий текст програми буде набувати то одного то іншого сенсу в залежності від чинників оточення. Плаваюча ж арифметика має апроксімативну природу, намагаючись побудувати семантику програми на апроксімаціях ми зруйнуємо детермінізм.

бути детермінованим
має апроксімативну природу
побудувати семантику програми на апроксімаціях
зруйнуємо детермінізм

шта?
а если по теме: 0.3/0.6666 дас всегда один и тот же результат с точностью до бита в 64х битах дабл на одном и том же типе цпу?

Маєте якісь публікації на цю тему? І що таке

одном и том же типе цпу

в триплеті build-host-target? Із власного свіжого досвіду — результати обчислень програми зібраної gcc під mingw64 та linux64 відрізняються в останньому біті при граничних значеннях exp.

Маєте якісь публікації на цю тему?

все мои публикации на гитхабе или под нда

Із власного свіжого досвіду — результати обчислень програми зібраної gcc під mingw64 та linux64 відрізняються в останньому біті при граничних значеннях exp

но они одинаковые при повторной компиляции?
если да, то результат детерминирован. дикси

но они одинаковые при повторной компиляции?

Завтра приходит чуть более новая версия библиотеки, в которой ошибка какого-нибудь sin() уменьшена в половине случаев с 1.5ULP до 1ULP, и всё — ваша детерминированность сломалась.

Справедливости ради надо сказать, что такие чудеса последние лет 20 происходят только с трансцедентными функциями. Четыре действия арифметики, pow(), вспомогательные функции типа ldexp — дают один и тот же результат на всех процессорах, поддерживающих IEEE754, при любом окружении (или покажите контрпример, я выпишу пару лучей сглаза их авторам).

И в glibc, по-моему, давно идеальная работа на всех таргетах. А вот уже freebsdʼшная libm (основанная в основном на старой msun) систематически подвирает на 0.5ULP. Не страшно, но «за державу обидно».

Технічна стаття на ДОУ про улюблені плюси українською — ранок починається чудово. Дяка автору.

Хоча на практиці я б швидше якийсь jsoncpp використав або допиляв Boost.Serialization

Дякую, що знайшли час прочитати. Що до практичного використання то практики бувають різні. Запропонований тут спосіб використовує лише фронтенд фічі компілятора. Якщо для зберігання даних використовувати типи даних без алокаторів то і скомпільваний код теж буде allocation-free і його можна буде використати на Class 0 Constrained Devices

Здесь больше идея, с конкретным примером. А для джисона есть rapidjson и еще стадо.

В rapidjson та інших DOM-парсерах користувачу бібліотеки потрібно ітерувати по DOM щоб витягти звідти дані. В запропонованому рішенні все відбувається automagically :)

У тебя уже и парсер выше есть?
И не только DOM там.

Вище — ні, немає. Бо ж стаття про шаблони. Є на гітхабі, ще для для C++11 та gcc-4.9

Вот и говорю, что спасибо за подход с новыми фичами. А джисон — это отдельно.

отлично. побольше бы таких статей

Дякую, що знайшли час прочитати.

Хороша стаття! Статичну рефлексію чекаємо аж в С++23 :(

Между прочим, есть неофициальная шаблонная библиотека, которая поддерживает рефлексию! Она не попала в boost, поскольку на последнем шаге она требует reinterpret_cast, но она прекрасно распознает все базовые поля POD структур, используя только C++11. Например, она была успешно применена для автоматической сериализации С-структур в больших легаси проектах, для которых раньше надо было указывать конкретные методы write() и read() для каждой из структур.

www.youtube.com/watch?v=abdeAew3gmQ — это объяснение работы библиотеки от автора. Сама библиотека — сходу не нашел, погуглите.

Вік живи вік учись :)
Слайди тут goo.gl/PA7Zb6
Сама бібліотека тут: github.com/apolukhin/magic_get

Дякую, що знайшли час прочитати.

После слов

Отже, витративши трішки часу на вивчення шаблонів з auto параметрами та написавши 160 рядків коду, ми створили можливість писати доволі складні JSON-об’єкти

я подсознательно сделал «facepalm» и разбил себе нос

Дякую Тобі, Боже, що пишу на дотнет коре!!!

Отлично, будет больше спрос на С++ программистов =)

Ну так не всем же Вэб-формочки ковырять.
Так есть народ и на ASM пишет, я думаю там еще более жесткая жесть.

OK. И шо? Это какое-то достижение? Так народ есть и ямы руками копает (я кино про Африку смотрел)

Ну таки да, достижение...

Если бы не ASM, C++, всякие умные компиляторы, фреймворки и шттп серверы, то вэбщики сейчас (вместо всяких nodeJS и фреймворков) писали бы что то типа такого —

FA 09 BB 45
6F 78 E8 FF

То есть всякие nodeJS и фреймворки — это достижение людей, которые в 2018 году пишут на ASM и плюсами сериализуют json? Неужели эти люди так действительно считают? Если да, то уровень ЧСВ — Бог

То есть всякие nodeJS и фреймворки — это достижение людей, которые в 2018 году пишут на ASM

- зачем прикидываться шлангом в 2018 году? Фреймворки это надстройки над фундаментом, нода работает поверх С\С++, 90% процентов всего в 2018 работает поверх С\С++, следовательно ответ на ваш вопрос — да, это достижения людей которые пишут в том числе и на низкоуровневых языках.

Ага, нода работает поверх С++, а полка работает поверху молотка.

Для каждой задачи существует свой инструмент. С (про который я и слова не написал) и С++ — инструменты. И, кстати, в задаче написания ядра операционной системы (по крайней мере линух, винда и мак), поверху которого эта ваша нода работает, С++ участвует почти никак. Представляете?
ЧСВ такое ЧСВ

Викрутка та шуруповерт — теж інструменти і навіть для схожих завдань. Що до Linux та інших ядер — це все ж таки легасі код, хоч і з використанням абстракцій, запозичених з ООП

Викрутка та шуруповерт — теж інструменти

Отличная аллегория, вот я и говорю, при наличии шуруповёрта и умении им пользоваться лучше им и воспользоваться, чтобы минимизировать усилия.
Статья, кстати, хорошая. Изначальный мой коммент — просто юмор, и не должен был нести никакого негатива. Но тут налетели вы%бщики и начали не в тему бросаться названиями языков программирования, которых даже не знают. ЧСВ оно такое.
«Папа, а шо лучче — пулімьот чи танк?» ©

Зайти сюда, обосрать тему топика, влепить что то про .Net Core. ...Офигенная шутко-юмор получилась.

Странный чтоли? Перечитай мой изначальный коммент. Кто кого или что обс@рал? Обидчивый сильно?!

Похоже вас в детстве С++ чем то сильно оибидел.

Если кажется, креститься надо

«Прикидывается шлангом» тут как раз тот, кто не понимает разницы между C, C++ как C с классами, и C++ данного супервысокого полёта, который понимает пара тысяч человек на планете.
И C++ никак не низкоуровневый — он очень многоуровневый. Для кого-то к счастью, для кого-то к сожалению.

Я и не говорил что ++ только низкоуровневый язык.

C++ данного супервысокого полёта

в данном контексте ++ скорее всего используется для написания приложений юзерспейса, и судя по лычке TS, для embedded продуктов. ИМХО высокий полет на С++, который в большинстве случаев поддерживается из коробки, намного более оправдан чем наворачивание и поддержка высокоуровневых сред и фреймворков.

Для кого-то к счастью, для кого-то к сожалению.

тут самое главное у себя на проекте таких высокоуровневых джуном мидлов синьоров и даже архитектов вовремя выпасать и отстреливать ещё на взлёте отправляя в мир иной на другие страшно модные и перспективные проекты главное чтобы не коммуницировали даже с твоим (но тут уже даже решаемо) так что на самом деле всё просто.

ЗЫ: ну и да а если вдруг там насяльника «из этих» то тихонько сидеть держать лапу на пульсе невысовываться иметь в виду всё заранее ждать пока засыпется а засыпется обязательно и потом тихонечко помогать только очень тихонечко и незаметно чтобы ни в коем случае не порушить авторитет и не отправить весь «си++ супервысокого полёта код» в говнокод одним махом а то нэвдобно выйдэ я проверял ))

FA 09 BB 45
6F 78 E8 FF

У вас во второй строчке две ошибки.

В asm разве есть json?

И чего вы к этому жисону приколупались.
Задача статьи показать как работают новые фичи в с++17.

как работают новые фичи в с++17.

а кроме жисону они умеют чо? ))

www.bfilipek.com/...​017/01/cpp17features.html

Вот тут вагон и маленькая тележка.
Дерзайте, мож еще статью про сериализацию в xml напишите.

статью про сериализацию в xml напишите.

а зачем? есть же ж жава и сишарп ))

ЗЫ: о кстати годная вещь мне как раз ннада была воспользуюсь!

        const char* dlm = "";
        out << "[";
        for(auto & i : value) {
            out << dlm << i;
            dlm = ",";
        }
        return out << "]";

и будет чтото типа [0,1,2,]

После 2ки запятой вроде не должно быть.

Нет там жести, но геморно. Ну и gcc-8 очень уже крут в этой части.

я честно впервые слышу чтоб формошлеперы выделывались)

Ну так не всем же Вэб-формочки ковырять.

Прошу прощения, а собсно для чего еще нужен JavaScript Object Notation?

Для представления произвольных структурированных данных без зафиксированной схемы в любых видах хранилищ и взаимодействий.

произвольных структурированных данных без зафиксированной схемы

тут вы батенька мягко говоря выдумавши и «произвольности» никакой нет зато есть жОсткая структуированность и вместе с ней более чем ЗАфиксированная схема любой желающий таки может взять любой json и применить к нему ту самую «произвольность без зафиксированности» и увидеть что получилось.

Прошу расшифровать эту мысль, я её в таком изложении не понимаю.

Чего приборы?

и как мы все время без этого жили!

3 экрана кода, можно было бы заменить тремя строчками ...

Без боли жизнь скучна

Я подозреваю , что эти 3 или 133 (но меньше 160) строчки будут не на С++.

Можно, например, так:

#[macro_use]
extern crate serde_derive; // строчка раз
extern crate serde_json; // строчка два

#[derive(Serialize, Deserialize)]
struct Pdo { ... }
#[derive(Serialize, Deserialize)]
struct Cdo { ... }

fn main() {
    let cdo = Cdo { ... };
    let cdo_str = serde_json::to_string(&cdo).unwrap(); // строчка три!
    println!("{}", cdo_str);
}
play.rust-lang.org/...​9fbeddeb23c8966282358a7f4

о, класс! а теперь считаем строки в serde_derive и в serde_json
а то у автора тоже в итоге в несколько строк код:

int main() {
vector v { 13, 17 };
Pdo obj { true, 11, "ss\"s" };
Cdo cdo { {false, 15, „Cdo”}, 1.2, {1, 2, 3, 5, 7}, {{true, 17, „cdo.pdo”}} };

cout << Array(v) << endl;
cout << Array(cdo.array) << endl;
Object::write(obj, cout) << endl;
cout << obj << endl;
cout << cdo << endl;
return 0;
}

Условия «строчки в библиотеках тоже считаются» не было :) Ну решение автора все равно не умеет экранировать спецсимволы, так что в реальной жизни туда обязательно добавится какая-нибудь либа.

Дякую за уважність — дійсно я обмежився лише quoted тому що стаття про шаблони а не парсери/генератори. А замінити quoted на самописний quoted_escaped не так вже й складно

Цікаво чому ви не дали приклад з JSON.stringify() & :)

Тому що Rust має приблизно ті самі області застосування, як і C++, це така собі наступна версія C++. Конкретно для вас він, мабуть, не підійде, але деякі речі (як, наприклад, оця) в ньому реалізовані зручніше.
Дякую за статтю, було цікаво.

Техническая статья, на доу, да еще и в пятницу. Протестую!

Подписаться на комментарии