Замените varargs шаблонами variadi c - PullRequest
0 голосов
/ 10 января 2020

Я использую C ++ 17 с шаблонами и рекурсией для замены C Va_Args. В настоящее время поддерживаются только числа с плавающей запятой, после работы с плавающей точкой следуют другие типы;)

class CWrite
{
public:
    template<typename NextT, typename ...RestT>
    static std::string Format(NextT next, RestT ... rest);

private:

    template<typename T>
    static constexpr bool is_float = std::is_same_v<T, float>;

    template<typename T>
    static constexpr bool IsValidParam();

    template<typename LastT>
    static std::string Format(LastT last);

    ///Empty param case
    static std::string Format();

};

// +++++++++++++++++++  Implementation ++++++++++++++++++++++++++

template<typename T>
constexpr bool CWrite::IsValidParam()
{
    bool bRes = false;
    bRes |= is_float<T>;
    return bRes;
}

template<typename NextT, typename ...RestT>
std::string CWrite::Format(NextT next, RestT ... rest)
{
    std::string strRes = Format(next);
    strRes += Format(rest...);
    return strRes;
}

template<typename LastT>
std::string CWrite::Format(LastT last)
{

    std::string strRes;
    if (is_float<LastT>)
    {
        strRes = "float:";
        char buffer[10] = { };
        snprintf(buffer, 10, "%f", last);
        strRes += buffer;
    }

    return strRes;
}

///Empty param case
std::string CWrite::Format()
{
    return "";
}

вызов этого с

std::string strRes = CWrite::Format(1.0f, 2.0f, 3.0f, 4.0f, 5);

приводит к предупреждению для snprintf() format '%f 'ожидает аргумент типа' double ', но аргумент 4 имеет тип' int '

Я ожидаю, что IsValidParam вернет false для последнего аргумента, который должен быть int .

https://onlinegdb.com/B1A72GHgU

Не могли бы вы помочь мне здесь? Я что-то здесь пропустил?

Ответы [ 2 ]

2 голосов
/ 10 января 2020

Если вы можете использовать C ++ 17, вы должны использовать if constexpr в следующей функции

template<typename LastT>
std::string CWrite::Format(LastT last)
{

    std::string strRes;

    // VVVVVVVVV  <-- add "constexpr" here
    if constexpr (is_float<LastT>)
    {
        strRes = "float:";
        char buffer[10] = { };
        snprintf(buffer, 10, "%f", last);
        strRes += buffer;
    }

    return strRes;
}

Проблема в том, что при использовании простого if вместо if constexpr компилятор скомпилировать оператор (часть внутри { ... }) также, когда is_float<LastT> ложно.

Если вы не можете использовать C ++ 17 ... Я полагаю, вы можете дифференцировать функцию через перегрузку

std::string CWrite::Format (float last)
 {    
   std::string strRes { "float:" };

   char buffer[10] = { };
   snprintf(buffer, 10, "%f", last);

   return strRes += buffer;
 }

std::string CWrite::Format (int last)
 {    
   std::string strRes { "int:" };

   char buffer[10] = { };
   snprintf(buffer, 10, "%i", last);

   return strRes += buffer;
 }
1 голос
/ 10 января 2020

В ответе max66 указана причина, по которой у вашего метода возникла проблема со строкой форматирования, и способы ее устранения. По сути, вам просто нужен какой-то способ выбора другой строки формата в зависимости от типа форматируемого значения.

Однако я хотел бы указать на еще один недостаток: вы предполагаете, что для любого данного значения потребуется только 9 символы для преобразования в строку. Для очень больших значений (например, 1e22) это не удастся. G CC фактически выдаст вам предупреждение, если сможет определить это во время компиляции.

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

Также ваше решение не проверяет ошибки формата (snprintf() возвращает отрицательный в этом случае). И в таких случаях вы можете добавить неопределенную память к вашей строке, поскольку я не уверен, что стандарт C гарантирует нулевое завершение буфера при сбое (но это может быть) .

Мое решение состоит в том, чтобы иметь функцию, которая форматирует данный аргумент на месте в конце std::string. Кроме того, он обрабатывает ошибки форматирования и случаи, когда 9 байтов недостаточно для хранения отформатированного значения.

Кроме того, я накладываю ограничения SFINAE на типы аргументов, чтобы гарантировать, что он может вызываться только с поддерживаемыми нами типами.

Вот мое решение с комментариями, чтобы объяснить, что делает, что и почему:

#include <string>
#include <type_traits>
#include <iostream>

// checks if T is a type we support
template<typename T>
inline constexpr bool allowed_type = std::is_floating_point_v<T> || std::is_integral_v<T>;

// the initial amount of space for stringifying each argument
constexpr std::size_t APPEND_PADDING = 20;

// returns the appropriate format string for type T (T assumed to be supported)
template<typename T>
const char *fmt_string()
{
    if constexpr (std::is_floating_point_v<T>) return "%f";
    else return "%d";
}

// stringifys val onto the end of str (T assumed to be supported)
template<typename T>
void append(std::string &str, T val)
{
    std::size_t prev_size = str.size();     // remember the previous size of str
    str.resize(prev_size + APPEND_PADDING); // allocate the space we need
    const char *fmt = fmt_string<T>();      // get the format string to use

    // format the value and check the save the return value
    int res = snprintf(&str[prev_size], APPEND_PADDING, fmt, val);

    // on format error, just skip it (or )
    if (res < 0) str.resize(prev_size);

    // if we didn't have enough room we need to try again with the correct size
    if ((std::size_t)res >= APPEND_PADDING)
    {
        str.resize(prev_size + res + 1); // make space for the characters we need and the null terminator
        snprintf(&str[prev_size], res + 1, fmt, val); // format the string again (this time will work)
        str.pop_back(); // remove the null terminator
    }
    // otherwise we had enough room, so just truncate to the written characters
    else str.resize(prev_size + res);
}

// formats all of args into a single string (only allows supported types)
template<typename ...Args, std::enable_if_t<(allowed_type<Args> && ...), int> = 0>
std::string format(Args ...args)
{
    std::string str;                               // create an empty buffer string to store the result
    str.reserve(sizeof...(args) * APPEND_PADDING); // predict how much space we'll need for everything
    int _[] = { (append(str, args), 0)... };       // append all the args to str one at a time
    (void)_; // suppress unused variable warnings (will just be optimized away)
    return str;
}

int main()
{
    std::cout << format(1, 2, 2.3, 3, 4.4, 5, 1e22) << '\n';
}

Обратите внимание, что при этом все отформатированные строки выполняются без разделения. Исправить это было бы так же просто, как изменить строки формата, возвращаемые с fmt_string().

Я использовал имена других функций, чем вы, но вы поняли идею. format() - это функция, которую вы бы использовали.

...