Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template variable as string instead of const char *

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?

like image 724
DilithiumMatrix Avatar asked Aug 21 '12 19:08

DilithiumMatrix


2 Answers

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 .

like image 92
orlp Avatar answered Sep 22 '22 22:09

orlp


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.

like image 30
j_random_hacker Avatar answered Sep 21 '22 22:09

j_random_hacker