10

我一直在尝试为 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>)'

这实际上是有道理的。

这对我来说不是什么大问题,我可以通过其他方式做到这一点,但我真的很想知道是否有办法绕过这个限制。

4

3 回答 3

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。基本上,获取重载和/或模板函数的地址通常是模棱两可的,但在特定上下文中是允许的。Initialization 和 static_casting 是其中两个上下文,因为它们提供了足够的类型信息来消除歧义。这允许模板参数推导正常进行。

显式模板参数非常诱人,但它们可以以各种方式爆炸。std::endl 不太可能以破坏此处显式模板参数的方式进行更改,但我真的建议不要使用它们(除非是专门为它们设计的东西,例如 forward 和 make_shared)。

于 2013-06-15T01:01:46.120 回答
10

问题是像这样的操纵器std::endl是函数模板。因此,您必须明确要传递该函数模板的哪个特化(否则,不可能进行类型推导)。

例如:

f(1, 2, 3, &std::endl<char, std::char_traits<char>>);
//                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
于 2013-06-15T00:39:55.003 回答
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 的提议,允许使用一些随机进一步滥用[]语法而不是依赖宏来自动生成重载集函子)。

现场示例,我将我的方法传递endl_functor给一个print方法,然后<<毫不费力地使用它。

于 2013-06-16T06:22:11.563 回答