问题
I have an api that looks like this:
template<typename... Args>
Widget::Widget(std::string format_str, Args&&... args);
How would you call this method if you have a vector of strings for 'args', i.e. the args length isn't known at compile time?
What would the implementation of a wrapper function look like that would convert this to something like this?
template<typename... Args>
Widget::WrapperWidget(std::string format_str, vector<string>);
回答1:
Following may help:
#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]...)
{}
};
回答2:
The Widget::Widget
function actually does not exist, it's only a template. A function is instantiated only after you specify the number and types of the parameters. This must be done in compile time.
Since the length of the vector is only available in runtime, I can think of only this solution:
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
}
Note that those f
functions are totally different functions in your executable.
Although I'm not sure that I'd like to code above in actual code.
If you wrote the interface than maybe you should extend the interface itself. If not, you may have overloads for this function.
Also reading the parameter names reveals that this is just some kind of pretty printing. You could also make a runtime counterpart to that function and pass as a single argument like this:
string s = format_vector_runtime(fmt, v);
f("%s", s);
where f
is still the widget constructor like above and the first parameter is a format string meaning "take the second parameter as is" - may be different for your Widget
.
回答3:
The size of the vector is actually of no concern. I drew up another example of how to process a vector that's supposed to replace stuff in the format string with a variadic function-template approach. You can easily adapt that to work with variadic class-templates. The basic idea is the same:
#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;
}
Basically, we want to iterate over the formate string and as soon as we hit a format specifier (in our case always represented by %s
), we print stuff that is currently the head element resulting from pack expansion. If that element is a vector, we want to iterate further over the format string as long as
- we don't hit the end of the format string and
- we still have elements in the vector to process
Just like with printf()
, superfluous arguments are ignored, if too many format specifiers are found after pack expansion has terminated, we throw an exception.
Note that the idea is inspired by Stroustrups's approach to emulate printf
via variadic templates - only my version takes a std::string
as format string instead of a const char*
.
The good thing is that you can easily make the function more powerful simply by providing another printHelper
overload that handles other types that don't provide an operator<<(std::ostream&[, ...])
or cannot be expanded to do so.
All the overload needs to do is handle adjustment of the format-string iterator and print stuff when a format specifier is hit. In my two examples, I do it with a look-ahead of 1 to determine we hit a format specifier, but there are other ways.
Incidentally, the output is:
Hello, StackOverflow! Welcome to the world of variadic template magic! This ... is ... C++11!
来源:https://stackoverflow.com/questions/21330153/c-vector-wrapper-around-variadic-function