使用模板编译函数在可变参数模板函数中失败
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();
}
我遇到了涉及可变参数模板的编译器错误。以下代码是一个高度简化的版本,它重现了我原始代码中的错误:
#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();
}