注意:以下代码是根据以下帖子修改的:https://stackoverflow.com/a/27375389
#include <iostream>
#include <type_traits>
template<class Head>
void print_args(std::ostream& s, Head&& head) {
s << std::forward<Head>(head);
}
template<class Head, class... Tail>
void print_args(std::ostream& s, Head&& head, Tail&&... tail) {
if (std::is_same<Head, uint8_t>::value)
s << static_cast<int>(head) << ","; // cast uint8_t so that the value of 1 or 0 can be displayed correctly in console
else
s << std::forward<Head>(head) << ",";
print_args(s, std::forward<Tail>(tail)...);
}
template<class... Args>
void print_args(Args&&... args) {
print_args(std::cout, std::forward<Args>(args)...);
}
int main()
{
uint8_t val = 1;
print_args(std::string("hello"), val); // error: invalid static_cast from type 'std::basic_string<char>' to type 'int'
print_args("hello", val); // error: invalid static_cast from type 'const char [6]' to type 'int'
}
问题>我需要将uint_8
强制转换为int
,以便可以在控制台中正确显示该值。但是,上面的代码为std::string
或const char*
构建了问题。
该功能的修复是什么?
在运行时评估if
,以便编译器必须为它编译两个分支,并且static_cast<int>(head)
无法编译非算术类型(例如std::string
)。
在C ++ 17中,您可以使用if constexpr(std::is_same<Head, uint8_t>::value)
解决这个问题。
在C ++ 17之前,添加一个可以为uint8_t
重载的额外函数:
template<class T>
auto convert(T&& t) -> decltype(std::forward<T>(t)) {
return std::forward<T>(t);
}
int convert(uint8_t t) {
return t;
}
template<class Head>
void print_args(std::ostream& s, Head&& head) {
s << convert(std::forward<Head>(head));
}
template<class Head, class... Tail>
void print_args(std::ostream& s, Head&& head, Tail&&... tail) {
s << convert(std::forward<Head>(head)) << ',';
print_args(s, std::forward<Tail>(tail)...);
}
你有几个问题。对于编译时问题:
if (std::is_same<Head, uint8_t>::value)
对此:
if constexpr (std::is_same<Head, uint8_t>::value)
那是因为你只想编译if的主体,当它转换为uint时是有效的。仅仅因为运行时if为false并不意味着其中的代码不必有效。 constexpr如果能解决这个问题。
接下来,您的类型比较过于严格。比较对非引用的引用将返回false。所以你想在is_same测试之前衰减类型。我也更喜欢is_same_v:
if constexpr (std::is_same_v<std::decay_t<Head>, uint8_t>)
最后,你的基本情况,即最后一个元素,仍然需要相同类型的“if constexpr”来正确打印它,但是你只在主循环中应用了这个逻辑。
把它放在一起:
template<class Head>
void print_args(std::ostream& s, Head&& head) {
if constexpr (std::is_same_v<std::decay_t<Head>, uint8_t>)
s << static_cast<int>(head);
else
s << head << ",";}
template<class Head, class... Tail>
void print_args(std::ostream& s, Head&& head, Tail&&... tail) {
if constexpr (std::is_same_v<std::decay_t<Head>, uint8_t>)
s << static_cast<int>(head) << ",";
else
s << head << ",";
print_args(s, std::forward<Tail>(tail)...);
}
template<class... Args>
void print_args(Args&&... args) {
print_args(std::cout, std::forward<Args>(args)...);
}
您可以将常见的if constexpr分解为单个辅助函数,以减少冗余代码,使其保持干燥状态。但不是去那里,我会提出别的建议。
让它发挥作用并不总是最好的目标。递归模板可以在编译器中使用更多内存并减慢构建速度,因此如果它们产生相同的结果,则避免递归的解决方案会更好。
因此,考虑折叠表达式(c ++ 17)和lambda来打印代码:
所有上述内容都可以替换为:
template<class... Args>
void print_args(Args&&... args) {
([](auto&& arg)
{
if constexpr (std::is_same_v<std::decay_t<decltype(arg)>, uint8_t>)
std::cout << static_cast<int>(std::forward<Args>(arg));
else
std::cout << arg;
if (sizeof...(Args) > 1)
std::cout << ",";
}(args), ...);
}
打破它,它使用逗号运算符上的fold表达式,使用形式(f(x)op ...)的IILE(立即调用的Lambda表达式),其中f(x)是lambda(给定当前arg) ,“op”是用于对调用进行排序的逗号运算符。)第二个“if”用于阻止尾随逗号。
一种可能的方案:
#include <iostream>
#include <type_traits>
namespace detail
{
namespace cpp11
{
template<class arg>
void print(std::ostream& s, arg&& a, typename std::enable_if<std::is_same<typename std::remove_reference<typename std::remove_cv<arg>::type>::type, uint8_t>::value>::type* =0)
{
s << static_cast<int>(a) << ",";
}
template<class arg>
void print(std::ostream& s, arg&& a, typename std::enable_if<!std::is_same<typename std::remove_reference<typename std::remove_cv<arg>::type>::type, uint8_t>::value>::type* =0)
{
s << std::forward<arg>(a) << ",";
}
}
namespace cpp14
{
template<class arg>
void print(std::ostream& s, arg&& a, std::enable_if_t<std::is_same<typename std::remove_reference<typename std::remove_cv<arg>::type>::type, uint8_t>::value>* =0)
{
s << static_cast<int>(a) << ",";
}
template<class arg>
void print(std::ostream& s, arg&& a, std::enable_if_t<!std::is_same<typename std::remove_reference<typename std::remove_cv<arg>::type>::type, uint8_t>::value>* =0)
{
s << std::forward<arg>(a) << ",";
}
}
namespace cpp17
{
template<class arg>
void print(std::ostream& s, arg&& a)
{
if constexpr (std::is_same_v<std::remove_reference_t<std::remove_cv_t<arg>>, uint8_t>)
s << static_cast<int>(a) << ",";
else
s << std::forward<arg>(a) << ",";
}
}
namespace cpp20
{
template<class arg>
void print(std::ostream& s, arg&& a)
{
if constexpr (std::is_same_v<std::remove_cvref_t<arg>, uint8_t>)
s << static_cast<int>(a) << ",";
else
s << std::forward<arg>(a) << ",";
}
}
template<class Head>
void print_args(std::ostream& s, Head&& head)
{
//cpp11::print(s, std::forward<Head>(head));
//cpp14::print(s, std::forward<Head>(head));
//cpp17::print(s, std::forward<Head>(head));
cpp20::print(s, std::forward<Head>(head));
}
template<class Head, class... Tail>
void print_args(std::ostream& s, Head&& head, Tail&&... tail)
{
print_args(s, std::forward<Head>(head));
print_args(s, std::forward<Tail>(tail)...);
}
}
template<class... Args>
void print_args(Args&&... args)
{
detail::print_args(std::cout, std::forward<Args>(args)...);
}
int main()
{
uint8_t val = 1;
print_args(std::string("hello"), val);
print_args("hello", val);
}
几个额外的说明:
uint8_t
,但你也可能面临其他POD类型的问题,如int8_t
,byte
等。std::ostream
返回template<...> std::ostream& print(...) {...; return s;}
参数std::remove_cvref{_t}
和std::remove_cv{_t}
轻松实现std::remove_reference{_t}