我有一个看起来像这样的api:
template<typename... Args>
Widget::Widget(std::string format_str, Args&&... args);
如果你有'args'的字符串向量,你会如何调用这个方法,即在编译时不知道args长度?
包装器函数的实现看起来会将此转换为类似的东西?
template<typename... Args>
Widget::WrapperWidget(std::string format_str, vector<string>);
答案 0 :(得分:4)
Widget::Widget
函数实际上不存在,它只是一个模板。只有在指定参数的数量和类型后,才会实例化一个函数。这必须在编译时完成。
由于向量的长度仅在运行时可用,我只能想到这个解决方案:
switch (v.size())
{
case 0: f(fmt); break;
case 1: f(fmt, v[0]); break;
case 2: f(fmt, v[0], v[1]); break;
case 3: f(fmt, v[0], v[1], v[2]); break;
//... etc
}
请注意,这些f
函数在您的可执行文件中是完全不同的函数。
虽然我不确定我是否愿意在实际代码中进行上述编码。
如果你编写的界面比你可能扩展界面本身。如果没有,您可能会有此功能的重载。
同时阅读参数名称也表明这只是一种漂亮的打印方式。您还可以创建该函数的运行时对应函数,并将其作为单个参数传递:
string s = format_vector_runtime(fmt, v);
f("%s", s);
其中f
仍然是上面的小部件构造函数,第一个参数是格式字符串,意思是“按原样取第二个参数” - 对于Widget
可能有所不同。
答案 1 :(得分:1)
矢量的大小实际上并不重要。我绘制了另一个例子,说明如何处理一个应该用格式变量函数模板方法替换格式字符串中的东西的向量。您可以轻松地调整它以使用可变参数类模板。基本思路是一样的:
#include <vector>
#include <string>
#include <iostream>
#include <exception>
using StrCIter = std::string::const_iterator;
void printerHelper(StrCIter& fmtPos, StrCIter fmtEnd, std::vector<std::string>& vec)
{
// if the vector is empty, we simply return without doing anything
// and instead print the next argument
auto vecIter = vec.begin ();
while(vecIter != vec.end () && fmtPos != fmtEnd)
{
if(*fmtPos == '%' && *(fmtPos + 1) == 's')
{
std::cout << *vecIter++;
fmtPos += 2;
continue;
}
std::cout << *fmtPos++;
}
}
template <typename T>
void printerHelper(StrCIter& fmtPos, StrCIter fmtEnd, const T& value)
{
std::cout << value;
fmtPos += 2;
}
void myPrintfHelper(StrCIter pos, StrCIter end)
{
// end of expansion - no more format arguments, just print remaining characters
while(pos != end)
{
if(*pos == '%')
{
throw "More format specifiers than arguments provided!";
}
std::cout << *pos++;
}
}
template <typename Head, typename ... Tail>
void myPrintfHelper(StrCIter pos, StrCIter end, Head&& head, Tail&&... tail)
{
while(pos != end)
{
if(*pos == '%' && *(pos + 1) == 's')
{
printerHelper (pos, end, head);
return myPrintfHelper(pos, end, std::forward<Tail>(tail)...);
}
std::cout << *pos++;
}
}
template <typename ... Args>
void myPrintf(const std::string& format, Args&& ... args)
{
myPrintfHelper (format.begin(), format.end (), std::forward<Args>(args)...);
}
int main()
{
std::vector<std::string> v = {"world", "magic"};
myPrintf("Hello, %s! Welcome to the %s of variadic template %s! This ... is ... C++%s!", "StackOverflow", v, 11);
return 0;
}
基本上,我们想要迭代格式字符串,一旦我们遇到格式说明符(在我们的例子中总是由%s
表示),我们就打印出当前由包扩展产生的头元素的东西。如果该元素是向量,我们希望在格式字符串上进一步迭代,只要
与printf()
一样,多余的参数被忽略,如果在包扩展终止后找到太多格式说明符,我们会抛出异常。
请注意,这个想法的灵感来自于Stroustrups's approach通过可变参数模板模拟printf
- 只有我的版本将std::string
作为格式字符串而不是const char*
。
好处是,您可以通过提供另一个printHelper
重载来轻松地使功能更强大,该重载可以处理其他未提供operator<<(std::ostream&[, ...])
或无法扩展的类型。< / p>
所有重载需要做的是处理格式字符串迭代器的调整并在命中格式说明符时打印东西。在我的两个例子中,我通过预测1来确定我们确定了格式说明符,但还有其他方法。
顺便提一下,输出是:
你好,StackOverflow!欢迎来到可变模板魔法的世界!这......是... C ++ 11!
答案 2 :(得分:0)
以下可能会有所帮助:
#if 1 // Not in C++11
#include <cstdint>
template <std::size_t ...> struct index_sequence {};
template <std::size_t I, std::size_t ...Is>
struct make_index_sequence : make_index_sequence < I - 1, I - 1, Is... > {};
template <std::size_t ... Is>
struct make_index_sequence<0, Is...> : index_sequence<Is...> {};
#endif // make_index_sequence
// you may use `std::tostring`
template <typename T> std::string myToString(T&& t);
class Widget
{
public:
Widget(std::string format_str, const std::vector<std::string>& v);
// this will call Widget(std::string, const std::vector<std::string>&)
template<typename... Args>
explicit Widget(std::string format_str, Args&&... args) :
Widget(format_str, std::vector<std::string>{myToString(std::forward<Args>(args))...})
{}
// This will call Widget(format_str, a[0], a[1], .., a[N - 1]) // So the Args&&... version
template <std::size_t N>
Widget(std::string format_str, const std::array<std::string, N>& a) :
Widget(format_str, a, make_index_sequence<N>())
{}
private:
template <std::size_t N, std::size_t...Is>
Widget(std::string format_str, const std::array<std::string, N>& a, const index_sequence<Is...>&) :
Widget(format_str, a[Is]...)
{}
};