我一直在尝试为std :: cout编写一个线程安全的包装器,并认为这是学习一些可变参数模板的好时机。
然后,当我认为我做对了,我注意到它不适用于std :: endl。
拿这段代码:
template <typename... P>
void f(P...){}
int main()
{
f(1,2,3,std::endl);
}
当你尝试编译它时,GCC以一种非常愚蠢的方式抱怨:
main.cpp:18:19: error: too many arguments to function 'void f(P ...) [with P = {}]'
当您使用常规模板尝试时,您将获得
main.cpp:22:13: error: no matching function for call to 'f(<unresolved overloaded function type>)'
实际上是有意义的。
这对我来说不是一个大问题,我可以用其他方式来做,但我真的很想知道是否有办法绕过这个限制。
答案 0 :(得分:14)
而不是Andy Prowl建议的显式模板参数,我推荐模板参数推导:
C:\Temp>type meow.cpp
#include <iostream>
#include <utility>
using namespace std;
void Print() { }
template <typename T, typename... Rest> void Print(T&& t, Rest&&... rest) {
cout << forward<T>(t);
Print(forward<Rest>(rest)...);
}
int main() {
ostream& (* const narrow_endl)(ostream&) = endl;
Print("Hello, world!", narrow_endl, "I have ", 1729, " cute fluffy kittens.",
static_cast<ostream& (*)(ostream&)>(endl)
);
}
C:\Temp>cl /EHsc /nologo /W4 /MTd meow.cpp
meow.cpp
C:\Temp>meow
Hello, world!
I have 1729 cute fluffy kittens.
N3690 13.4 [over.over]指定此处使用的规则,这些规则可以追溯到C ++ 98。基本上,获取重载和/或模板化函数的地址通常是模糊的,但在特定上下文中是允许的。初始化和static_casting是其中两个上下文,因为它们提供了足够的类型信息来消除歧义。这允许模板参数推断正常进行。
显式模板参数非常诱人,但它们可以以各种方式爆炸。 std :: endl不太可能以一种破坏显式模板参数的方式进行更改,但我确实建议不要使用它们(除非专门为它们设计了东西,比如forward和make_shared)。
答案 1 :(得分:10)
问题在于像std::endl
这样的操纵器是功能模板。因此,您必须明确要传递的函数模板的哪个特化(否则,不能进行类型推导)。
例如:
f(1, 2, 3, &std::endl<char, std::char_traits<char>>);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
答案 2 :(得分:2)
template
个函数不是函数,std::endl
是template
函数。
您无法传递template
功能。但是,您可以传递一个表示重载集的函数对象。编写这样的仿函数非常简单:
struct endl_overloadset {
template<typename... Args>
auto operator()(Args&&...args)const
->decltype(std::endl( std::forward<Args>(args) ) )
{ return ( std::endl( std::forward<Args>(args) ) ) };
template<typename T,typename=typename std::enable_if<\
std::is_same< decltype(static_cast<T>( std::endl )), T >::value\
>::type>\
operator T() const { return std::endl; }
};
但我发现它有点像样板文件,所以写一些宏为你做的工作:
#define RETURNS(X) ->decltype(X) { return (X); } // C++11 lacks non-lambda return value deduction
#define OVERLOAD_SET(F) struct {\
template<typename... Args>\
auto operator()(Args&&...args)const\
RETURNS( F( std::forward<Args>(args)... ) )\
template<typename T,typename=typename std::enable_if<\
std::is_same< decltype(static_cast<T>( F )), T >::value\
>::type>\
operator T() const { return F; }\
}
static OVERLOAD_SET(std::endl) Endl;
然后将Endl
传递给您的f
,并调用Endl(Blah)
结束std::endl(Blah)
。类似地,将Endl
分配给变量或将其传递给方法与将std::endl
分配给变量或将其传递给方法(wrt重载分辨率)基本相同。
遗憾的是,OVERLOAD_SET
无法在函数中使用,因为本地类型不能使用template
方法。如果它可以在函数中使用,那么:
f(1,2,3, OVERLOAD_SET(std::endl)() );
会做你想要的。但那将是您想要编程的语言,而不是您拥有的语言。 (甚至更好的是@ Xeo的建议允许使用[]
语法的随机进一步滥用来自动生成重载集函子,而不是依赖宏。
Live example,我将endl_functor
传递给print
方法,然后在其上使用<<
而不再赘述。