用于创建格式化字符串的可变参数模板
Variadic template for creating a formatted string
我想创建一个格式化方法,使用给定的 typename
参数生成一个字符串。我为此使用可变参数模板:
template<typename T>
std::string GetFormat()
{
std::string ret;
if (typeid(T) == typeid(int))
ret = "i";
else if (typeid(T) == typeid(float))
ret = "f";
else
ret = "n";
return ret;
}
template<typename... Args>
std::string GetFormatVArgs()
{
std::string ret;
// for each arg in Args
// ret += GetFormat<arg>()
return ret;
}
void main()
{
std::string str = GetFormatVArgs<float, float, int>();
std::cout << str;
}
预期输出:
ffi
如何迭代每个 typename
并将其交给 GetFormat()
?
好吧,实际上可能有更简单的方法来执行此操作,但现在开始:
void helper(std::string& out)
{}
template<typename T, typename... Ts>
void helper(std::string& out, T*, Ts... ts)
{
out += GetFormat<T>();
helper(out, ts...);
}
template<typename... Ts>
std::string GetFormatVArgs()
{
std::string ret;
helper(ret, typename std::add_pointer<Ts>::type()...);
return ret;
}
我以前制作过可变参数函数,但它们总是有参数,因此可以利用类型推导,但我想不出没有类型推导的简单方法。所以我让主 GetFormatVArgs
函数创建了一个可变指针列表,我可以将其传递给辅助函数。这样我就可以利用类型推导。我创建指针而不是对象的原因是因为您可能想使用不能默认构造的类型,或者默认构造的类型很昂贵。
我认为这可能是解决您问题的最简单方法。与 Benjamin Lindley 的解决方案不同,这不依赖于类型推导,而是依赖于 SFINAE。
template <typename T, typename... Args, typename std::enable_if<sizeof...(Args) == 0>::type* = nullptr>
std::string GetFormatVArgs() { return typeid(T).name(); }
template <typename T, typename... Args, typename std::enable_if<sizeof...(Args) != 0>::type* = nullptr>
std::string GetFormatVArgs() { return typeid(T).name() + GetFormatVArgs<Args...>();
}
这是我的解决方案
using namespace std;
class END {};
template <typename TYPE>
class GetFormatChar
{
};
template<> class GetFormatChar<int> { char c={'i'}; };
template<> class GetFormatChar<float> { char c={'f'}; };
template<> class GetFormatChar<double> { char c={'d'}; };
template<> class GetFormatChar<END> { char c={'[=10=]'}; };
template<typename HEAD, typename ... ARGS>
class Format_Impl: GetFormatChar<HEAD>, Format_Impl<ARGS...>
{
};
template<typename HEAD>
class Format_Impl<HEAD>: GetFormatChar<HEAD>,GetFormatChar<END>
{
};
template <typename ... ARGS>
class Format: Format_Impl<ARGS...>
{
public:
constexpr char* GetFormat() { return (char*)(this); }
};
int main()
{
Format<int,double,float,int> f;
std::cout << f.GetFormat() << endl;
}
它的作用和工作原理:
它只是根据给定模板参数的类型从 class 创建一个对象。每个只包含一个字符。此 char 在每个构造函数中初始化。 (编辑:直接在 class 定义中初始化)。
因为没有其他数据成员,字符是逐字节排列的。这个简单意味着我们有一个字符串。此外,我们需要一个结尾'\0',它是通过最后一个模板的特化创建的,只有一个参数,序列在这里停止。现在我们简单的把自己的对象转成字符串,因为我们知道我们的内存布局是"the same"作为字符串
所以它在编译时使用专业化,而不是在 运行 期间!
编辑:不需要构造函数,只需使用初始化,我们有 c++11 :-)
与此处介绍的其他解决方案相比,它没有任何 运行 时间成本。在 运行 时不需要字符串+ 操作。该字符串直接在内存中可用。如果对象是静态创建的,则字符串将在数据 space 中准备,根本不需要任何额外费用。
好的,金属编码有点难,但它确实有效。 (如果没有,请给我反馈 :-))
第二个解决方案来了,它非常简单直接,不使用任何对话和技巧。
- 不需要 RTTI!
- 完全没有运行成本!
- 未使用 std::string 且未使用其他运行时函数!
所有数据都是静态初始化的,这将通过从应用程序中的数据区域复制来完成,而无需在启动后的运行时产生任何成本。
class GetChar
{
public:
template <typename T> static constexpr const char Get();
};
template<>constexpr const char GetChar::Get<int>() { return 'i';}
template<>constexpr const char GetChar::Get<double>() { return 'd';}
template<>constexpr const char GetChar::Get<float>() { return 'f';}
template <typename ... ARGS>
class Format2
{
static const char string[sizeof...(ARGS)+1];
public:
constexpr static const char* GetFormat() { return string; }
};
template <typename ... ARGS>
const char Format2<ARGS...>::string[sizeof...(ARGS)+1]={GetChar::Get<ARGS>()...,'[=10=]'};
int main()
{
cout << Format2<int,double,float,int>::GetFormat() << endl;
}
我按照 Klaus 的建议解决了它,但更简单一些
template<typename T>
char GetFormat() { return 'n'; }
template<>
char GetFormat<int>() { return 'i'; }
template<>
char GetFormat<float>() { return 'f'; }
template <typename... Args>
std::string GetFormatVArgs()
{
char formats[] = { GetFormat<Args>()... , '[=10=]'};
std::string ret(formats);
return ret;
}
我想创建一个格式化方法,使用给定的 typename
参数生成一个字符串。我为此使用可变参数模板:
template<typename T>
std::string GetFormat()
{
std::string ret;
if (typeid(T) == typeid(int))
ret = "i";
else if (typeid(T) == typeid(float))
ret = "f";
else
ret = "n";
return ret;
}
template<typename... Args>
std::string GetFormatVArgs()
{
std::string ret;
// for each arg in Args
// ret += GetFormat<arg>()
return ret;
}
void main()
{
std::string str = GetFormatVArgs<float, float, int>();
std::cout << str;
}
预期输出:
ffi
如何迭代每个 typename
并将其交给 GetFormat()
?
好吧,实际上可能有更简单的方法来执行此操作,但现在开始:
void helper(std::string& out)
{}
template<typename T, typename... Ts>
void helper(std::string& out, T*, Ts... ts)
{
out += GetFormat<T>();
helper(out, ts...);
}
template<typename... Ts>
std::string GetFormatVArgs()
{
std::string ret;
helper(ret, typename std::add_pointer<Ts>::type()...);
return ret;
}
我以前制作过可变参数函数,但它们总是有参数,因此可以利用类型推导,但我想不出没有类型推导的简单方法。所以我让主 GetFormatVArgs
函数创建了一个可变指针列表,我可以将其传递给辅助函数。这样我就可以利用类型推导。我创建指针而不是对象的原因是因为您可能想使用不能默认构造的类型,或者默认构造的类型很昂贵。
我认为这可能是解决您问题的最简单方法。与 Benjamin Lindley 的解决方案不同,这不依赖于类型推导,而是依赖于 SFINAE。
template <typename T, typename... Args, typename std::enable_if<sizeof...(Args) == 0>::type* = nullptr>
std::string GetFormatVArgs() { return typeid(T).name(); }
template <typename T, typename... Args, typename std::enable_if<sizeof...(Args) != 0>::type* = nullptr>
std::string GetFormatVArgs() { return typeid(T).name() + GetFormatVArgs<Args...>();
}
这是我的解决方案
using namespace std;
class END {};
template <typename TYPE>
class GetFormatChar
{
};
template<> class GetFormatChar<int> { char c={'i'}; };
template<> class GetFormatChar<float> { char c={'f'}; };
template<> class GetFormatChar<double> { char c={'d'}; };
template<> class GetFormatChar<END> { char c={'[=10=]'}; };
template<typename HEAD, typename ... ARGS>
class Format_Impl: GetFormatChar<HEAD>, Format_Impl<ARGS...>
{
};
template<typename HEAD>
class Format_Impl<HEAD>: GetFormatChar<HEAD>,GetFormatChar<END>
{
};
template <typename ... ARGS>
class Format: Format_Impl<ARGS...>
{
public:
constexpr char* GetFormat() { return (char*)(this); }
};
int main()
{
Format<int,double,float,int> f;
std::cout << f.GetFormat() << endl;
}
它的作用和工作原理:
它只是根据给定模板参数的类型从 class 创建一个对象。每个只包含一个字符。此 char 在每个构造函数中初始化。 (编辑:直接在 class 定义中初始化)。
因为没有其他数据成员,字符是逐字节排列的。这个简单意味着我们有一个字符串。此外,我们需要一个结尾'\0',它是通过最后一个模板的特化创建的,只有一个参数,序列在这里停止。现在我们简单的把自己的对象转成字符串,因为我们知道我们的内存布局是"the same"作为字符串
所以它在编译时使用专业化,而不是在 运行 期间!
编辑:不需要构造函数,只需使用初始化,我们有 c++11 :-)
与此处介绍的其他解决方案相比,它没有任何 运行 时间成本。在 运行 时不需要字符串+ 操作。该字符串直接在内存中可用。如果对象是静态创建的,则字符串将在数据 space 中准备,根本不需要任何额外费用。
好的,金属编码有点难,但它确实有效。 (如果没有,请给我反馈 :-))
第二个解决方案来了,它非常简单直接,不使用任何对话和技巧。
- 不需要 RTTI!
- 完全没有运行成本!
- 未使用 std::string 且未使用其他运行时函数!
所有数据都是静态初始化的,这将通过从应用程序中的数据区域复制来完成,而无需在启动后的运行时产生任何成本。
class GetChar
{
public:
template <typename T> static constexpr const char Get();
};
template<>constexpr const char GetChar::Get<int>() { return 'i';}
template<>constexpr const char GetChar::Get<double>() { return 'd';}
template<>constexpr const char GetChar::Get<float>() { return 'f';}
template <typename ... ARGS>
class Format2
{
static const char string[sizeof...(ARGS)+1];
public:
constexpr static const char* GetFormat() { return string; }
};
template <typename ... ARGS>
const char Format2<ARGS...>::string[sizeof...(ARGS)+1]={GetChar::Get<ARGS>()...,'[=10=]'};
int main()
{
cout << Format2<int,double,float,int>::GetFormat() << endl;
}
我按照 Klaus 的建议解决了它,但更简单一些
template<typename T>
char GetFormat() { return 'n'; }
template<>
char GetFormat<int>() { return 'i'; }
template<>
char GetFormat<float>() { return 'f'; }
template <typename... Args>
std::string GetFormatVArgs()
{
char formats[] = { GetFormat<Args>()... , '[=10=]'};
std::string ret(formats);
return ret;
}