【问题标题】:Is there some trick that would allow me to pass stream manipulators to a variadic template function?是否有一些技巧可以让我将流操纵器传递给可变参数模板函数?
【发布时间】:2013-06-11 17:25:32
【问题描述】:

我一直在尝试为 std::cout 编写一个线程安全的包装器,并认为现在是学习一些可变参数模板的好时机。

Like that.

然后,就在我认为我做对了的时候,我注意到它不适用于 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>)'

这实际上是有道理的。

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

【问题讨论】:

    标签: c++ templates c++11 variadic-templates


    【解决方案1】:

    我推荐模板参数推导,而不是 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)。

    【讨论】:

    • 所以没有办法编写允许用户使用常规名称的修饰符,而不是像narrow_endl这样的东西?
    • @user697683 如果您要编写一个包装 ostream 的打印函数,您不妨引入用于格式化的 wrappe 功能(即不允许在您的界面中使用 ostream 操纵器并提供替代方案) .但是你最终会在 ostream 之上实现一些 printf,这很可能是在 C printf 之上实现的。
    【解决方案2】:

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

    例如:

    f(1, 2, 3, &std::endl<char, std::char_traits<char>>);
    //                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    【讨论】:

      【解决方案3】:

      template 函数不是函数,std::endltemplate 函数。

      您不能传递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 方法,然后毫不费力地在其上使用&lt;&lt;

      【讨论】:

      • 请注意,这种转换后的操纵器不能再像真正的操纵器一样使用了:std::cout &lt;&lt; OVERLOAD_SET(std::endl){};(甚至std::cout &lt;&lt; []std::endl;)不会编译,因为这两个不产生正常的功能,但功能对象。
      • @xeo operator T 添加并测试! endl_functor 现在转换为 std::endl 将转换为的相同内容。
      猜你喜欢
      • 2017-07-08
      • 2023-03-14
      • 1970-01-01
      • 2021-09-28
      • 2014-10-20
      • 2013-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多