Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / C++ Новый топик    Ответить
Топик располагается на нескольких страницах: [1] 2 3   вперед  Ctrl      все
 Вариативные шаблоны C++ и вариативные функции Си  [new]
petrav
Member

Откуда:
Сообщений: 2105
По мотивам статьи вот здесь.

Если коротко, человек написал вариативный шаблон функции, которая есть быть обёрткой вокруг `std::printf()`. Вариативные аргументы шаблона передаются «as is» в `printf()`. Если отбросить нюансы семантики копирования и т.п. Ну...

Вопрос № 1: насколько это соответствует стандарту С++? Они бинарно совместимы?

Идём далее. Пример из его статьи:

template <typename ... Args>
void Print(char const * const format,
           Args const & ... args) noexcept
{
    printf(format, Argument(args)...);
}


Вопрос № 2: в какой последовательности согласно стандарту вызывается функция `Argument()` для вариативных шаблонных параметров? Это определено стандартом или нет?
25 апр 20, 16:19    [22122681]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
Anatoly Moskovsky
Member

Откуда: Odessa
Сообщений: 6494
petrav
Вопрос № 1: насколько это соответствует стандарту С++? Они бинарно совместимы?

Соответствует.
Бинарная совместимость не требуется. Это два ортогональных механизма.
Распаковка пакета параметров оператором "..." - это просто синтаксический сахар и все работает также как если бы вы аргументы передавали в printf явным перечислением.
petrav
Вопрос № 2: в какой последовательности согласно стандарту вызывается функция `Argument()` для вариативных шаблонных параметров? Это определено стандартом или нет?

Порядок вычисления аргументов неопределен (IIRC, кроме некоторых исключений для перегруженных операторов в свежих версиях стандарта) независимо от того как подставляются аргументы, из вариативного пакета параметров или явным перечислением. А какая разница? Это не влияет на рабочесть примера.
25 апр 20, 19:03    [22122747]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
petrav
Member

Откуда:
Сообщений: 2105
Anatoly Moskovsky
petrav
Вопрос № 1: насколько это соответствует стандарту С++? Они бинарно совместимы?

Соответствует.
Бинарная совместимость не требуется. Это два ортогональных механизма.
Распаковка пакета параметров оператором "..." - это просто синтаксический сахар и все работает также как если бы вы аргументы передавали в printf явным перечислением.

Окей, я Вас понял.

Anatoly Moskovsky
petrav
Вопрос № 2: в какой последовательности согласно стандарту вызывается функция `Argument()` для вариативных шаблонных параметров? Это определено стандартом или нет?

Порядок вычисления аргументов неопределен (IIRC, кроме некоторых исключений для перегруженных операторов в свежих версиях стандарта) независимо от того как подставляются аргументы, из вариативного пакета параметров или явным перечислением. А какая разница? Это не влияет на рабочесть примера.

А разница **большая**. Я хочу разработать обёртку вокруг `printf()` которая хотя бы на этапе исполнения проверяла строку формата и типы переданных аргументов. Лучше на этапе компиляции.
25 апр 20, 19:19    [22122759]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
petrav
Member

Откуда:
Сообщений: 2105
Anatoly Moskovsky,

Для этого мне нужно что бы функция `Argument()` вызывалась последовательно в вышеприведённом примере.
25 апр 20, 19:21    [22122762]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
Anatoly Moskovsky
Member

Откуда: Odessa
Сообщений: 6494
petrav
А разница **большая**. Я хочу разработать обёртку вокруг `printf()` которая хотя бы на этапе исполнения проверяла строку формата и типы переданных аргументов. Лучше на этапе компиляции.

Для этого мне нужно что бы функция `Argument()` вызывалась последовательно в вышеприведённом примере.

Зачем для этого последовательно вызывать какую-то функцию для каждого аргумента?
25 апр 20, 20:21    [22122794]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
petrav
Member

Откуда:
Сообщений: 2105
Anatoly Moskovsky
petrav
А разница **большая**. Я хочу разработать обёртку вокруг `printf()` которая хотя бы на этапе исполнения проверяла строку формата и типы переданных аргументов. Лучше на этапе компиляции.

Для этого мне нужно что бы функция `Argument()` вызывалась последовательно в вышеприведённом примере.

Зачем для этого последовательно вызывать какую-то функцию для каждого аргумента?

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

Для не стандартных типов срабатывает шаблонная перегрузка и там `static_assert`.


Но лучше конечно compile time.

PS: Да, придётся написать свой парсер формата `printf()`, но я думаю это макс на пару дней работы без написания тестов. Т.е. на коленке.
25 апр 20, 20:34    [22122799]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
petrav
Member

Откуда:
Сообщений: 2105
Anatoly Moskovsky,

Если у Вас есть какое-то лучшее решение в стиле:

for (auto const &curr: Parametrs)
{
    checkFormat(parser, curr);
}


Так Вы сообщите, плиз. Я не особо изучал С++17.
25 апр 20, 21:09    [22122822]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
Anatoly Moskovsky
Member

Откуда: Odessa
Сообщений: 6494
petrav,

template <typename ... Args>
bool validate_format(const char* fmt)
{
    // распарсить формат и проверить его соответствие типам Args 
    // например используя рекурсивное применение вспомогательного шаблона к Args
    return ...;
}


template <typename ... Args>
void my_print(const char* fmt, Args ... args)
{
    if (!validate_format<Args...>(fmt))
        throw std::logic_error("invalid format");
    printf(fmt, args...);
}


Это в рантайме.
В компайл-тайме тоже наверно как-то можно, лень думать.
25 апр 20, 22:02    [22122837]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
a guest
Member

Откуда:
Сообщений: 255
petrav
Но лучше конечно compile time.

PS: Да, придётся написать свой парсер формата `printf()`, но я думаю это макс на пару дней работы без написания тестов. Т.е. на коленке.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat-error")
Проверка форматной строки в compile-time готова.
25 апр 20, 22:07    [22122839]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
petrav
Member

Откуда:
Сообщений: 2105
Anatoly Moskovsky

Это в рантайме.

Это замечательно, но...

template <typename ... Args>
bool validate_format(const char* fmt)
{
    // распарсить формат и проверить его соответствие типам Args 
    // например используя рекурсивное применение вспомогательного шаблона к Args
    return ...;
}

Я примерно понимаю о чём вы говорите. Наш рекурсивый шаблон зависит от типа первого параметра, он наследуется от самого себя, но предок зависит от типа второго параметра и т.д. Да? Но как это сделать?! Как пронавигировать по Args? Пожалуйста. :)

Anatoly Moskovsky

В компайл-тайме тоже наверно как-то можно, лень думать.

Ну мы тут ни куда же не торопимся. Лень, так лень. Подумаем через неделю.
25 апр 20, 22:19    [22122843]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
Anatoly Moskovsky
Member

Откуда: Odessa
Сообщений: 6494
petrav,

struct FmtArg
{
    // some type info
};

template <typename T>
bool validate_format_arg(FmtArg& fmt_arg)
{
    // check if T can be used with FmtArg
    return true;
}

// recursion terminator
template <size_t>
bool validate_format_args(std::vector<FmtArg>&)
{
    return true;
}

template <size_t ArgPos, typename T, typename ... Rest>
bool validate_format_args(std::vector<FmtArg>& parsed_fmt)
{
    if (!validate_format_arg<T>(parsed_fmt[ArgPos]))
        return false;
    return validate_format_args<ArgPos + 1, Rest...>;
}

std::vector<FmtArg> parse_format(const char* fmt)
{
    // implement real parse
    std::vector<FmtArg> rv;
    while (auto c = *fmt++) {
        if (c == '%')
            rv.push_back({});
    }
    return rv;
}

template <typename ... Args>
bool validate_format(const char* fmt)
{
    // распарсить формат и проверить его соответствие типам Args
    auto parsed_fmt = parse_format(fmt);
    if (parsed_fmt.size() != sizeof...(Args))
        return false;
    // например используя рекурсивное применение вспомогательного шаблона к Args
    return validate_format_args<0, Args...>(parsed_fmt);
}


template <typename ... Args>
void my_print(const char* fmt, Args ... args)
{
    if (!validate_format<Args...>(fmt))
        throw std::logic_error("invalid format");
    printf(fmt, args...);
}

int main()
{
    my_print("%i %s\n", 2, "a");
    return 0;
}
25 апр 20, 23:48    [22122883]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
Cerebrum
Member

Откуда: Омикрон Персей 8
Сообщений: 7937
petrav,

рекурсивные шаблоны - зло, особенно для компилятора, их место уже занял fold expression

в качестве примера https://www.qt.io/blog/efficient-qstring-concatenation-with-c17-fold-expressions
26 апр 20, 09:38    [22122955]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
Anatoly Moskovsky
Member

Откуда: Odessa
Сообщений: 6494
Cerebrum
рекурсивные шаблоны - зло, особенно для компилятора, их место уже занял fold expression

Вообще-то fold expr не применим к типам.
Так что пока не добавят в стандарт итерацию по списку типов придется использовать рекурсивные шаблоны.
26 апр 20, 13:38    [22123050]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
Anatoly Moskovsky
Member

Откуда: Odessa
Сообщений: 6494
В принципе если еще дальше перейти в рантайм, то можно избавиться от рекурсивных шаблонов.

struct FmtArg
{
    // some type info
};

bool compatible_formats(const FmtArg& a, const FmtArg& b)
{
    // check a is compatible with b
    return true;
}

template <typename T>
FmtArg make_format()
{
    // make FmtArg from T
    return FmtArg{};
}

std::vector<FmtArg> parse_format(const char* fmt)
{
    // implement real parse
    std::vector<FmtArg> rv;
    while (auto c = *fmt++) {
        if (c == '%')
            rv.push_back({});
    }
    return rv;
}

template <typename ... Args>
bool validate_format(const char* fmt)
{
    // распарсить формат и проверить его соответствие типам Args
    auto parsed_fmt = parse_format(fmt);
    if (parsed_fmt.size() != sizeof...(Args))
        return false;
    size_t i = 0;
    for (auto& arg_fmt: { make_format<Args>() ... }) {  // тут превращаем типы в значения
        if (!compatible_formats(parsed_fmt[i++], arg_fmt))
            return false;
    }
    return true;
}


template <typename ... Args>
void my_print(const char* fmt, Args ... args)
{
    if (!validate_format<Args...>(fmt))
        throw std::logic_error("invalid format");
    printf(fmt, args...);
}

int main()
{
    my_print("%i %s\n", 2, "a");
    return 0;
}


PS. Правда fold expressions и тут не нужны ))
26 апр 20, 14:00    [22123066]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
mayton
Member

Откуда: loopback
Сообщений: 46496
Какой смысл затаскивать в обсуждение Си? Это - контр-продуктивно вообще.
26 апр 20, 14:00    [22123067]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
Cerebrum
Member

Откуда: Омикрон Персей 8
Сообщений: 7937
Anatoly Moskovsky
Вообще-то fold expr не применим к типам.

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

я привел пример, в котором производится конкатенация строк, но кто мешает расширить состав обрабатываемых типов, раз уж тут пытаются сэмулировать работу printf, набор типов которой описывается форматной строкой, где каждому типу сопоставлена лексема?

+ Если примера недостаточно, то вот еще один,

в котором автор, перед исполнением ODBC запроса, использует выражения свертки для подготовки буферов переменных (разных типов, состав которых расширяем). В итоге variadic pack разбирается на составляющие и к каждому типу применяется свой собственный обработчик на базе специализированного типом шаблона-функтора
26 апр 20, 15:18    [22123103]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
petrav
Member

Откуда:
Сообщений: 2105
Anatoly Moskovsky
В принципе если еще дальше перейти в рантайм, то можно избавиться от рекурсивных шаблонов.

Ого... А говорили думать лень. Я правда просил только пример рекурсивного шаблона, но Ваш развёрнутый код очень познавателен. Спасибо!

Но уходить желательно в compile time. Если взять за основу Ваш первый прототип... То имхо, std::vector не нужен. Проходить по строке формата нужно одновременно с навигацией по типам. Оказались в перегрузке для double -- начали искать в строке формата `%f`. Тогда состояние парсера будет описываться одним указателем на char. И, наверное, можно будет сделать compile time.
26 апр 20, 15:21    [22123104]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
petrav
Member

Откуда:
Сообщений: 2105
mayton
Какой смысл затаскивать в обсуждение Си? Это - контр-продуктивно вообще.

Может printf() кривой, но он очень удобен и производителен. Тут у меня код программный... ему уже лет 30-ть. Тут вообще хардкор нереал. :) Но он работает...
26 апр 20, 15:26    [22123108]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
Cerebrum
Member

Откуда: Омикрон Персей 8
Сообщений: 7937
petrav,

на мой взгляд пройтись по типам в variadic pack'e - вообще не проблема, проблема будет в том, чтобы в компайл тайме обеспечить парсинг фоматной строки, если каждому типу в ней не будет отведено строго обозначенное кол-во символов, чего в реальном printf не было никогда.

Например, тип може быть задан с префиксом указывающем точность округления - %0.5f, и тогда это уже не будет %f для compile-time парсера.

Если же у вас форматная строка всегда четко вида %[type symbol], то можно попробовать в complle time ее разобрать на составляющие (я не пробовал, но если форматная строка - лексема, то думаю, что это реально). А типы сопоставить - это фигня.
26 апр 20, 15:29    [22123111]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
petrav
Member

Откуда:
Сообщений: 2105
Cerebrum
petrav,

на мой взгляд пройтись по типам в variadic pack'e - вообще не проблема, проблема будет в том, чтобы в компайл тайме обеспечить парсинг фоматной строки, если каждому типу в ней не будет отведено строго обозначенное кол-во символов, чего в реальном printf не было никогда.

Например, тип може быть задан с префиксом указывающем точность округления - %0.5f, и тогда это уже не будет %f для compile-time парсера.

Если же у вас форматная строка всегда четко вида %[type symbol], то можно попробовать в complle time ее разобрать на составляющие (я не пробовал, но если форматная строка - лексема, то думаю, что это реально). А типы сопоставить - это фигня.

Нет проблем написать парсер и %f и %0.5f и вообще всех вариантов. Это простой формат, это ж не регулярные выражения. По крайней мере я так думаю.

За ссылки спасибо, прочитаю.

Вот Вы бы написали бы свой прототип? Но не стоит задачи написать свою библиотеку ввода вывода. Нужно создать обёртку вокруг printf().

Сообщение было отредактировано: 26 апр 20, 15:37
26 апр 20, 15:37    [22123116]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
petrav
Member

Откуда:
Сообщений: 2105
mayton
Какой смысл затаскивать в обсуждение Си? Это - контр-продуктивно вообще.

Ещё меня привлекает идея расширить стандартный формат:
std::printf("%5.3ang", angle); // Тут автоматически перевод радиан в углы.

Конечно, перед передачей в оригинальный printf() нужно и строку формата подкорректировать до стандартной. Но это уже в run time, конечно.
26 апр 20, 15:59    [22123124]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
mayton
Member

Откуда: loopback
Сообщений: 46496
Я думаю что тебе нужна Scala и ее implicit conversions.
Ты - постигнешь дзен и твоя душа придёт в состояние гармонии.
26 апр 20, 16:51    [22123145]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
petrav
Member

Откуда:
Сообщений: 2105
Очередной этап изысканий.

Cerebrum

Например, тип може быть задан с префиксом указывающем точность округления - %0.5f, и тогда это уже не будет %f для compile-time парсера.


Вроде работает. Есть конечно вероятность, что в определённый момент компилятор не выдержит сложности парсера. И я не уверен, что третий тестовый формат правильный. Но такой вариант вообще запретить нужно. Сузить стандартный формат, а потом расширить нестандартными форматами -- до задач предметной области.

constexpr bool checkCharOrNo(char const *&frmt, char const ch)
{
    if (*frmt == ch)
    {
        ++frmt;
        return true;
    }
    return false;
}

constexpr bool checkDigitOrNo(char const *&frmt)
{
    while (*frmt >= '0' && *frmt <= '9')
    {
        ++frmt;
    }
    return true;
}

constexpr bool checkFloat(char const *frmt)
{
    while (*frmt != '\0')
    {
        if (checkCharOrNo(frmt, '%'))
        {
            checkDigitOrNo(frmt);
            if (checkCharOrNo(frmt, '.'))
            {
                checkDigitOrNo(frmt);
            }
            return *frmt == 'f';
        }
        ++frmt;
    }
    return false;
}

static_assert(checkFloat("Angle: %f."));
static_assert(checkFloat("Angle: %10f."));
static_assert(checkFloat("Angle: %10.f.")); // Запретить в своём парсере.
static_assert(checkFloat("Angle: %10.3f."));
26 апр 20, 16:56    [22123153]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
Anatoly Moskovsky
Member

Откуда: Odessa
Сообщений: 6494
Cerebrum
прекрасно пременим, если их состав известен (при необходимости может быть расширен за счет добавления нужных специализаций).

Не применим. Потому что в С++ нет бинарных операторов применимых к типам. А fold expression это применение операторов к списку сущностей. И поскольку есть только операторы применимые к значениям, то fold expression возможен только для пакета параметров, но не пакета типов этих параметров.
26 апр 20, 19:05    [22123220]     Ответить | Цитировать Сообщить модератору
 Re: Вариативные шаблоны C++ и вариативные функции Си  [new]
petrav
Member

Откуда:
Сообщений: 2105
Господа, нам осталось решить одну проблему: как в одном вызове совместить и статическую проверку данных и динамическую печать данных? Желательно найти выход без макросов. Неужели выхода нет?! :-((( Вот псевдокод, он не компилируется:

constexpr bool check(char const *str)
{
    return str != nullptr;
}

constexpr void print_f(char const *str)
{
    static_assert(check(str)); // Compile time.
    std::printf(str);          // Run time.
}

А вот жуткое решение на вариативных макросах:

#if (DEBUG)
#define print_f(format, ...)                       \
    static_assert(checkArgs(format, __VA_ARGS__)); \
    printStr(format, __VA_ARGS__);
#elif (RELEASE)
// В Release хотим компилироваться без макросов. Для очистки совести.
#endif

template <size_t Size, typename... Parameters>
constexpr bool checkArgs(char const (&format)[Size],
                         Parameters const &... parameters)
{
    // В прототипе проверим длину формата и наличие нуля.
    char const *end = format;
    while (*end++ != '\0');
    size_t const len = end - format - 1;
    return len>0 && len<128;
}

// Тут начинается тихий ужас. В зависимости от конфигурации имя функции печати
// должно быть разным.
template <size_t Size, typename... Parameters>
constexpr void
#if (DEBUG)
printStr
#else
print_f
#endif
(char const (&format)[Size], Parameters const &... parameters)
{
    if (!checkArgs(format, parameters...)) throw -1; // Для Release.
    std::snprintf(buff, std::size(buff), format, parameters...);
}

void newTest()
{
    print_f("Angle: %0.3f. Mode: %s.", 12.8, "Prepare");
}


Заранее спасибо!
1 май 20, 09:17    [22126073]     Ответить | Цитировать Сообщить модератору
Топик располагается на нескольких страницах: [1] 2 3   вперед  Ctrl      все
Все форумы / C++ Ответить