用可变参数模板替换可变参数
Replace varargs with variadic templates
我正在使用带有模板和递归的 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()
的警告
格式“%f
”需要类型为“double
”的参数,但参数 4 的类型为“int
”
我希望最后一个参数 IsValidParam
returns false
应该是 int
.
https://onlinegdb.com/B1A72GHgU
你能帮帮我吗?
我错过了什么吗?
如果你能使用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;
}
max66 的回答解决了您的方法格式字符串有问题的原因以及如何修复它。基本上,您只需要根据要格式化的值的类型选择不同的格式字符串。
但是,我想指出另一个缺陷:您假设任何给定值只需要 9 个字符即可转换为字符串。对于非常大的值(例如 1e22
),这将失败。如果 GCC 可以在编译时确定这一点,它实际上会向您发出警告。
此外,您当前的实现分配了许多字符串并将它们递归地附加在一起。这当然是非常低效的,并且会降低 printf
系列函数的速度,以至于不值得使用它们。
此外,您的解决方案不检查格式错误(snprintf()
returns 在这种情况下为负数)。在这种情况下,您可能会在字符串上附加未定义的内存,因为我不确定 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()
是您要使用的函数。
我正在使用带有模板和递归的 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()
的警告
格式“%f
”需要类型为“double
”的参数,但参数 4 的类型为“int
”
我希望最后一个参数 IsValidParam
returns false
应该是 int
.
https://onlinegdb.com/B1A72GHgU
你能帮帮我吗? 我错过了什么吗?
如果你能使用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;
}
max66 的回答解决了您的方法格式字符串有问题的原因以及如何修复它。基本上,您只需要根据要格式化的值的类型选择不同的格式字符串。
但是,我想指出另一个缺陷:您假设任何给定值只需要 9 个字符即可转换为字符串。对于非常大的值(例如 1e22
),这将失败。如果 GCC 可以在编译时确定这一点,它实际上会向您发出警告。
此外,您当前的实现分配了许多字符串并将它们递归地附加在一起。这当然是非常低效的,并且会降低 printf
系列函数的速度,以至于不值得使用它们。
此外,您的解决方案不检查格式错误(snprintf()
returns 在这种情况下为负数)。在这种情况下,您可能会在字符串上附加未定义的内存,因为我不确定 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()
是您要使用的函数。