0

I have come across a compiler error involving variadic templates. The following code is a strongly simplified version which reproduces the error in my original code:

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

typedef std::vector<std::string> stringvec;

// dummy function: return string version of first vectorelemenr
template<typename T>
std::string vecDummy(const std::string sFormat, const T t) {
    std::stringstream ss("");
    if (t.size() > 0) {
        ss << t[0];
    }
    return  ss.str();    
}

// recursion termination
std::string recursiveY(stringvec &vFlags,  uint i) {
    return "";
}  

// walk through arguments
template<typename T, typename... Args>
std::string recursiveY(stringvec &vFlags,  uint i, T value, Args... args) {

    std::string sRes = "";
    if (vFlags[i] == "%v") {
        sRes += vecDummy(vFlags[i], value);
    }    
    sRes += " "+recursiveY(vFlags, i+1, args...);

    return sRes;
}

int main(void) {
    stringvec vPasta   = {"spagis", "nudle", "penne", "tortellini"};
    stringvec vFormats = {"%v", "%s"};

    std::string st = "";
    st += recursiveY(vFormats, 0, vPasta, "test12");
    std::cout << ">>" << st  << "<<" << std::endl;

    return 0;
} 

This simple code should walk through the arguments passed to recursiveY() and, if the current format string is "%v" it would pass the corresponding argument to vecDummy() which would return a string version of the vector's first element (if there is one).

The error message from the compiler is

sptest2.cpp: In instantiation of ‘std::string vecDummy(std::string, T) [with T = const char*; std::string = std::__cxx11::basic_string<char>]’:
sptest2.cpp:30:25:   required from ‘std::string recursiveY(stringvec&, uint, T, Args ...) [with T = const char*; Args = {}; std::string = std::__cxx11::basic_string<char>; stringvec = std::vector<std::__cxx11::basic_string<char> >; uint = unsigned int]’
sptest2.cpp:32:27:   required from ‘std::string recursiveY(stringvec&, uint, T, Args ...) [with T = std::vector<std::__cxx11::basic_string<char> >; Args = {const char*}; std::string = std::__cxx11::basic_string<char>; stringvec = std::vector<std::__cxx11::basic_string<char> >; uint = unsigned int]’
sptest2.cpp:43:21:   required from here
sptest2.cpp:12:11: error: request for member ‘size’ in ‘t’, which is of non-class type ‘const char* const’
   12 |     if (t.size() > 0) {
      |         ~~^~~~

It seems as if the compiler uses all types i pass to recursiveY() in main, but vecDummy() is designed to only work with vectors of some kind (and not with const char*, for example).

Is there a possibility to modify this code so that it will work as intended?

Is there perhaps a way of assuring the compiler that i will only pass vectors to vecDummy() (even at the risk of a runtime error or unexpected behaviour - similar to passing an integer to printf() when it expects a string)?

0

2 Answers 2

1

You can add an overload of vecDummy that handles the std::vector case and 'dumb down' the more general one to (say) just return an empty string:

// dummy function: return string version of first vectorelement (catch-all)
template<typename T>
std::string vecDummy(const std::string, const T) {
    return "";
}

// dummy function: return string version of first vectorelement (vectors only)
template<typename T>
std::string vecDummy(const std::string, const std::vector <T> &t) {
    std::stringstream ss("");
    if (t.size() > 0) {
        ss << t[0];
    }
    return  ss.str();    
}

Live demo

Sign up to request clarification or add additional context in comments.

Comments

1

I think if constexpr can help here. I'm posting two solutions. (And a third, more complete one, at the end after an edit I did some time after posting)

First solution (only the function vecDummy changes). Here, vecDummy receives parameters of all types, doing its intended work only for vectors and doing nothing when the parameter is not a vector.

template<typename T>
std::string vecDummy(
    [[maybe_unused]] const std::string sFormat,
    [[maybe_unused]] const T t
)
noexcept
{
    std::stringstream ss("");
    if constexpr (std::is_same_v<T, stringvec>) {
        if (t.size() > 0) {
            ss << t[0];
        }
    }
    else {
        // nothing, since this is intended to work only for vectors
    }
    return ss.str();
}

Another solution is to move if constexpr inside recursiveY (and leave vecDummy unchanged). In this solution, vecDummy is only called for parameters of vector-type and when the format is "%v".

template<typename T, typename... Args>
std::string recursiveY(
    const stringvec& vFormats,
    std::size_t i,
    T value, Args... args
)
noexcept
{
    std::cout << "(1) i= " << i << '\n';

    std::string res = "";

    if (vFormats[i] == "%v") {
        if constexpr (std::is_same_v<T, stringvec>) {
            res += vecDummy(vFormats[i], value);
        }
    }
    else {
        if constexpr (not std::is_same_v<T, stringvec>) {
            res += std::string(value);
        }
    }
    res += " " + recursiveY(vFormats, i+1, args...);

    return res;
}

EDIT

Following a question asked by OP in a comment, I've updated the solution to a more complete one in which std::vector<int> is allowed. Also, I've added the handling of the case "%s", which was lacking in the original post.

This update uses the construct is_vector that I found in this post.

The result of this code is >>spagis 1 1234 6789 test12 <<.

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

typedef std::vector<std::string> stringvec;

/* ------------------------------------------------ */
// copied from https://stackoverflow.com/a/12043020/12075306
template <typename T, typename _ = void>
struct is_vector {
    static const bool value = false;
};
template <typename T>
struct is_vector< T,
      typename std::enable_if<
          std::is_same<T,
                  std::vector< typename T::value_type,
                               typename T::allocator_type >
                 >::value
      >::type
    >
{
    static const bool value = true;
};
/* ------------------------------------------------ */

// dummy function: return string version of first vector element
template<typename T>
std::string vecDummy(
    [[maybe_unused]] const std::string sFormat,
    [[maybe_unused]] const std::vector<T>& t
)
noexcept
{
    std::stringstream ss("");
    if (t.size() > 0) { ss << t[0]; }
    return ss.str();
}

// recursion termination
std::string recursiveY
([[maybe_unused]] const stringvec& vFlags, [[maybe_unused]] std::size_t i) noexcept
{ return ""; }

template<typename T, typename... Args>
std::string recursiveY(
    const stringvec& vFormats,
    std::size_t i,
    T value, Args... args
)
noexcept
{
    std::cout << "(1) i= " << i << '\n';

    std::string res = "";
    if (vFormats[i] == "%v") {
        if constexpr (is_vector<T>::value) {
            res += vecDummy(vFormats[i], value);
        }
    }
    else {
        if constexpr (not is_vector<T>::value) {
            res += std::string(value);
        }
    }
    res += " " + recursiveY(vFormats, i+1, args...);

    return res;
}

int main(void) {
    stringvec vPasta        = {"spagis", "nudle", "penne", "tortellini"};
    std::vector<int> vMoney1 = {1, 2, 3, 4};
    std::vector<int> vMoney2 = {1234, 2, 3, 4};
    std::vector<int> vMoney3 = {6789, 2, 3, 4};
    stringvec vFormats = {"%v", "%v", "%v", "%v", "%s"};

    std::string st = "";
    st += recursiveY(vFormats, 0, vPasta, vMoney1, vMoney2, vMoney3, "test12");
    std::cout << ">>" << st  << "<<" << std::endl;

    return 0;
}

3 Comments

thank you for you suggestions. However, my code must also run on a machine that can't handle C++17 (it's not my machine, so i can't change/update the compiler).
Also, this code is intended to handle vectors of various types (string, int,...). For this the constexpr part would need to be something like "std::is_same_v<T, std::vector<U>" for some unspecified type U. Would there be a construct that can do this?
I believe that with the other answer, you cannot handle the case of "%s" without compiler errors. Compiler errors will arise. Notice that in my solution (yes, with C++17) the result of your main is: >>spagis test12 <<. Answering your second question: here you will find the construct to detect vectors. I've updated my solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.