I like vectors, and generally use them over arrays. For that reason I created a templated variadic function to initialize vectors (included below).
Header (.h):
template <typename T>
vector<T> initVector(const int argCount, T first, ...);
Source (.hpp):
template <typename T>
vector<T> initVector(const int argCount, T first, ...) {
vector<T> retVec;
retVec.resize(argCount);
if(argCount < 1) { ... }
retVec[0] = first;
va_list valist;
va_start(valist, first);
for(int i = 0; i < argCount-1; i++) { retVec[i+1] = va_arg(valist, T); }
va_end(valist);
return retVec;
}
It works great for most types (e.g. int, double...), but not for strings---as the compiler interprets them as 'const char *', thus
vector<string> strvec = initVector(2, "string one", "string two");
gives me the error:
error: conversion from ‘std::vector<const char*, std::allocator<const char*> >’ to non-scalar type ‘std::vector<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >’ requested
Is there any way to get the string arguments to be interpreted as strings without having to cast each one?
Because the type of the constant "string one"
is const char*
and not std::string
there needs to be a conversion. va_arg
can not make this conversion, so we need a second template argument:
template <typename VecT, typename EleT>
std::vector<VecT> init_vector(const size_t nargs, EleT first, ...) {
std::vector<VecT> result;
result.reserve(nargs);
if (nargs == 0) {
return result;
}
result.push_back(first);
if (nargs == 1) {
return result;
}
va_list valist;
va_start(valist, first);
for (int i = 1; i < nargs; ++i) {
result.push_back(VecT(va_arg(valist, EleT)));
}
va_end(valist);
return result;
}
std::vector<std::string> = init_vector<std::string>(2, "string one", "string two")
Note that I made some changes, most notably change resize
to reserve
, to prevent unnecessary objects from being created.
You can also simply use this (no risk of having number of elements messed up, and type safe):
const char *args[] = {"string one" , "string two"};
std::vector<std::string> strvec(args, args + sizeof(args)/sizeof(args[0]))
Or use C++11 initializer lists:
std::vector<std::string> strvec = {"string one" , "string two"};
For fun I made this little thing that is even neater and safer, but doesn't generalize into an arbitrary amount of arguments. It works by overloading. Here are the first three overloads and example usage:
template<class C>
inline C init_container() {
return C();
}
template<class C, class T>
inline C init_container(T arg0) {
const T args[1] = {arg0};
return C(args, args + 1);
}
template<class C, class T>
inline C init_container(T arg0, T arg1) {
const T args[2] = {arg0, arg1};
return C(args, args + 2);
}
std::vector<std::string> vec =
init_container< std::vector<std::string> >("hello", "world");
A full header (for up to 100 arguments) can be downloaded here: https://gist.github.com/3419369 .
Try using 2 template type parameters instead:
template <typename T, typename U>
vector<T> initVector(const int argCount, U first, ...) {
Very often (e.g. for int
, double
etc.) T
and U
will be the same. But the difference with the new strategy is that we now allow them to be different, provided that there is an implicit conversion from U
to T
(such as from const char*
to string
). This should be safe in the sense that, if no implicit conversion exists, you will get a compile-time error.
BTW interesting strategy -- it never occurred to me that va_list
etc. could be employed in this way! OTOH, I believe there is new machinery in C++11 that allows vectors etc. to be directly initialised from initialiser lists, similar to how you have always been able to initialise arrays in C like int a[] = { 3, 4, 5 };
, so it may be better to go with that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With