使用模板编译函数在可变参数模板函数中失败

Compiling a function with templates fails in variadic template function

我遇到了涉及可变参数模板的编译器错误。以下代码是一个高度简化的版本,它重现了我原始代码中的错误:

#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;
} 

这个简单的代码应该遍历传递给 recursiveY() 的参数,如果当前格式字符串是“%v”,它会将相应的参数传递给 vecDummy(),这将 return 向量第一个元素的字符串版本(如果有的话)。

编译器的错误信息是

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) {
      |         ~~^~~~

似乎编译器使用了我在 main 中传递给 recursiveY() 的所有类型,但 vecDummy() 被设计为仅适用于某种向量(而不适用于 const char* ,例如)。

是否可以修改此代码以使其按预期工作?

是否有一种方法可以确保编译器我只会将向量传递给 vecDummy()(即使存在运行时错误或意外行为的风险——类似于将整数传递给 printf()当它需要一个字符串时)?

我认为 if constexpr 可以提供帮助。我 post 有两个解决方案。 (还有第三个,更完整的,在编辑结束后我在 posting 之后做了一段时间)

第一个解决方案(只有函数vecDummy发生变化)。这里,vecDummy 接收所有类型的参数,仅对向量执行预期的工作,当参数不是向量时不执行任何操作。

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();
}

另一个解决方案是将 if constexpr 移到 recursiveY 中(并保持 vecDummy 不变)。在此解决方案中,仅在 vector-type 的参数和格式为 "%v".

时调用 vecDummy
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;
}

编辑

根据 OP 在评论中提出的问题,我已将解决方案更新为更完整的解决方案,其中允许 std::vector<int>。此外,我还添加了对大小写“%s”的处理,这是原始 post.

中所缺少的

此更新使用了我在 this post.

中找到的结构 is_vector

这段代码的结果是>>spagis 1 1234 6789 test12 <<

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

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

/* ------------------------------------------------ */
// copied from 
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;
}

您可以添加 vecDummy 的重载来处理 std::vector 情况,而 'dumb down' 更一般的情况(比如)只是 return 一个空字符串:

// 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