I'm working on understanding template parameter pack expansion in C . For demo purposes, I wanted to write a function that prints human readable names of types used in a signature (any signature - that's where the variadic template comes in).
#include <boost/type_index.hpp>
std::string SignatureToString()
{
return std::string();
}
template<typename Arg1, typename... Args>
std::string SignatureToString(const Arg1&, Args&&... args)
{
std::string strRetVal = boost::typeindex::type_id<Arg1>().pretty_name();
std::string strRemainingSignature = SignatureToString(args...); // expanding parameters works
if (!strRemainingSignature.empty())
{
strRetVal = strRetVal ", " strRemainingSignature;
}
return strRetVal;
}
class cDog {/* ... */};
int main(int /*argc*/, char* /*argv*/[])
{
int i(0);
std::string str;
//cDog someDog("Harry"); // don't want to construct this dummy object!
std::cout << "GetSignature(): '" << SignatureToString() << "'" << std::endl;
std::cout << "GetSignature(int): '" << SignatureToString(i) << "'" << std::endl;
std::cout << "GetSignature(std::string): '" << SignatureToString(str) << "'" << std::endl;
//std::cout << "GetSignature(cDog): '" << SignatureToString(someDog) << "'" << std::endl;
std::cout << "GetSignature(int, std::string): '" << SignatureToString(i, str) << "'" << std::endl;
//std::cout << "GetSignature(int, std::string, cDog): '" << SignatureToString(i, str, someDog) << "'" << std::endl;
}
The above example works. Since boost::typeindex::type_id does not really need the passed variable of type Arg1 (and some types - such as cDog - might not or not easily be dummy-constructable), I thought I could change the implementation to the one below (same template arguments, but no passed parameter pack of those types) and recursively call the template by expanding the pack of types instead of the pack of parameters. I read https://en.cppreference.com/w/cpp/language/parameter_pack section "Template argument lists" to mean that this should be possible, but my compiler (vc141) gives me error C2672 (no matching signature found).
edit: The error description apparently has to do with template resolution, but I don't see the applicability to my problem. https://learn.microsoft.com/en-gb/cpp/error-messages/compiler-errors-2/compiler-error-c2672?view=msvc-150
template<typename Arg1, typename... Args>
std::string SignatureToString()
{
std::string strRetVal = boost::typeindex::type_id<Arg1>().pretty_name(); // this line apparently still works (makes sense)
std::string strRemainingSignature = SignatureToString<Args...>(); // expanding type pack does not work (...?)
if (!strRemainingSignature.empty())
{
strRetVal = strRetVal ", " strRemainingSignature;
}
return strRetVal;
}
int main(int /*argc*/, char* /*argv*/[])
{
std::cout << "GetSignature(): '" << SignatureToString() << "'" << std::endl;
std::cout << "GetSignature(int): '" << SignatureToString<int>() << "'" << std::endl;
std::cout << "GetSignature(std::string): '" << SignatureToString<std::string>() << "'" << std::endl;
return 0;
}
What am I doing wrong here?
CodePudding user response:
The problem is in the stop condition of the recursion.
To stop the recursion you use
std::string SignatureToString()
In the first example you call it like
SignatureToString();
While in the second one you call it like
SignatureToString<>();
Which does not work since it’s not a template.
CodePudding user response:
If you have C 17 you can use if constexpr
and fold expressions. It's so much better than recursive function templates.
template<typename... Args>
std::string SignatureToString()
{
std::string ret;
if constexpr (sizeof...(Args) == 0) {
ret = "";
} else {
ret = ((boost::typeindex::type_id<Args>().pretty_name() ", ") ... );
}
return ret.substr(0, ret.size()-2);
}
CodePudding user response:
As the other Daniel pointed out, the problem is stopping the recursion. The following is not really pretty, but it works.
// Catch call with zero args
std::string SignatureToString()
{
return std::string();
}
// Catch call with exactly one arg
template <typename Arg1>
std::string SignatureToString()
{
return boost::typeindex::type_id<Arg1>().pretty_name();
}
// Catch all other cases
template<typename Arg1, typename Arg2, typename... Args>
std::string SignatureToString()
{
std::string strRetVal = boost::typeindex::type_id<Arg1>().pretty_name();
std::string strRemainingSignature = SignatureToString<Arg2, Args...>();
if (!strRemainingSignature.empty())
{
strRetVal = strRetVal ", " strRemainingSignature;
}
return strRetVal;
}