问题
I'm having trouble constructing a variadic template where the expansion functions properly match both a non-const and const std::string
. I have a generic matching function which is called instead in some cases.
I've reduced the code to the below example. Things which aren't a string should end up at the generic function. Things which are a string should end up at the second function which additionally outputs "STRING".
#include <iostream>
#include <string>
template<typename X, typename T>
void extend_exception( X & ex, T&& value )
{
ex << value << std::endl;
}
template<typename X>
void extend_exception( X & ex, std::string && value )
{
ex << "STRING: " << value << std::endl;
}
template<typename X, typename TF, typename ... TR>
void extend_exception( X & ex, TF&& f, TR&& ... rest )
{
extend_exception( ex, std::forward<TF>(f) );
extend_exception( ex, std::forward<TR>(rest)... );
}
int main()
{
extend_exception( std::cout, std::string( "Happy" ) );
std::string var( "var" );
extend_exception( std::cout, var );
extend_exception( std::cout, var, std::string( "Combo" ), const_cast<std::string const&>(var));
return 0;
}
In the version above the two var
uses do not get passed to the string function. I have tried various other ways of specifying the parameter but none seem to do the trick: std::string && value
, std::string const & value
, std::string const && value
. I did also try just std::string & value
and then the non-const strings match, but not the const ones. This provides me with at least a workaround by having two different functions, but I suspect I shouldn't need to do this.
How do I write a single function here that will be called for const, non-const, and temporary string objects?
I'm using g++ 4.6.3
回答1:
This is pretty tricky, T&&
is a so called universal reference but std::string&&
is not (no type deduction is involved). See here for a good explanation. Here is a fix:
template<typename X, typename T>
void extend_exception_( X & ex, T&& value, ... )
{
ex << value << std::endl;
}
template<typename X, typename T,
typename = typename std::enable_if<
std::is_same<std::string, typename std::decay<T>::type>::value>::type>
void extend_exception_( X & ex, T && value, int )
{
ex << "STRING: " << value << std::endl;
}
template <typename T> using identity = T;
template<typename X, typename ... R>
void extend_exception( X & ex, R&& ... rest )
{
identity<bool[]>{false,(extend_exception_(ex, std::forward<R>(rest), 0), false)...};
}
int main()
{
extend_exception( std::cout, std::string( "Happy" ) );
std::string var( "var" );
extend_exception( std::cout, var );
extend_exception( std::cout, var, std::string( "Combo" ) );
}
回答2:
This is because your string version only captures strings passed as rvalues. lvalues will get passed to the generic function. There is a huge difference between T&& and string&&, or more general T&& and U&&, if T is a template parameter and U is not (meaning, U is some specified type). See Scott Meyers' notes on universal references
What you need to do in order to make the function work as you want is add yet another overload:
template<typename X>
void extend_exception( X & ex, std::string const& value )
{
ex << "STRING: " << value << std::endl;
}
回答3:
You can't bind a non-rvalue to an rvalue-reference, so string&&
doesn't work, but const std::string&
works for var
. However, var
is not const, so the shortest path to an overload that works is collapsing T&&
to std::string&
and taking the first one.
You'll need to figure out types that are mutually exclusive or use SFINAE to write two that exclude each other on the right type of arguments.
回答4:
I revisited the problem and came up with a solution which involves using a class marker. I've written an article with the full solution/discussion.
来源:https://stackoverflow.com/questions/14241495/variadic-template-trouble-matching-const-and-non-const-stdstring