【问题标题】:What is wrong with this variadic templates example?这个可变参数模板示例有什么问题?
【发布时间】:2011-04-13 22:58:38
【问题描述】:

基类是:

#include <memory>

namespace cb{

template< typename R, typename ... Args >
class CallbackBase
{
public:
    typedef std::shared_ptr< CallbackBase< R, Args... > >
            CallbackPtr;

    virtual ~CallbackBase()
    {
    }
    virtual R Call(  Args ... args) = 0;
};
} // namespace cb

派生类是这样的:

namespace cb{
template< typename R, typename ... Args >
class FunctionCallback : public CallbackBase< R, Args... >
{
public:
    typedef R (*funccb)(Args...);

    FunctionCallback( funccb cb_ ) : 
        CallbackBase< R, Args... >(),
        cb( cb_ )
    {
    }
    virtual ~FunctionCallback()
    {
    }
    virtual R Call(Args... args)
    {
      return cb( args... );
    }
private:
  funccb cb;
};
} // namespace cb

创建函数:

namespace cb{
template < typename R, typename ...Args >
typename CallbackBase< R, Args... >::CallbackBasePtr
    MakeCallback( typename FunctionCallback< R, Args... >::funccb cb )
{
    typename CallbackBase< R, Args... >::CallbackBasePtr
        p( new FunctionCallback< R, Args... >( cb )
);
    return p;
}
} // namespace cb

还有例子:

bool Foo_1args( const int & t)
{
    return true;
}
int main()
{
    auto cbObj = cb::MakeCallback( & Foo_1args );
}

我不断收到此错误:

error: no matching function for call to ‘MakeCallback(bool (*)(const int&))’
error: unable to deduce ‘auto’ from ‘<expression error>’

我试图改变它,但我不知道如何解决。

那么,有什么问题吗?以及如何修复这个例子?

【问题讨论】:

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


    【解决方案1】:

    通过一个更简单的例子,这个问题可能是有意义的。尝试在这里找出问题:

    template <typename T>
    struct id { typedef T type; };
    
    template <typename T>
    void foo(typename id<T>::type x);
    
    foo(5); // error
    

    问题是编译器无法推断出T 应该是什么;它没有在任何地方直接使用。你必须明确地提供它:foo&lt;int&gt;(5),或者让它以其他方式推断它:

    template <typename T>
    void foo(typename id<T>::type x, T y);
    
    foo(5, 7); // okay, T is int because 7 is int
    

    这是有道理的:编译器如何确定提供给idT 导致id&lt;T&gt;::type 匹配?可能会有专业化,如果可能的话,整个事情无论如何都会很昂贵。


    同样,编译器无法推断出RArgs。相反,您应该这样做:

    template < typename R, typename ...Args >
    typename CallbackBase< R, Args... >::CallbackBasePtr
        MakeCallback( R cb(Args...) )
    {
        typename CallbackBase< R, Args... >::CallbackBasePtr
            p( new FunctionCallback< R, Args... >( cb ));
    
        return p;
    }
    

    最后,您还有其他需要修复的小问题,which Xeo has outlined

    【讨论】:

    • 应该与typenameR (*cb)(Args...) 一起使用。 :)
    • @Xeo:在typename 上是的,谢谢。但是这两种语法都适用于函数,类似于 int i[] 变成 int* i 的方式。我发现不放这三个字符更清楚。
    • 啊,对..忘记了函数到函数指针的自动转换,你是对的。但是该代码仍然存在问题,返回类型应该是 CallbackPtr 而不是 CallbackBasePtr,因为该 typedef 不存在(这是实际问题的一部分,因为 SFINAE 启动并将该函数从重载中取出设置)。
    • @VJo:GMan 是对的,args 是不可演绎的。主要原因是:::: 行为类似于“推导防火墙”,类型推导无法通过。
    • @VJo:我只能向你推荐this video,它很好地解释了 SFINAE 并告诉你,为什么这个论点是不可演绎的。总的来说,我可以推荐整个系列。
    【解决方案2】:

    回忆我在其他答案的 cmets 中提到的内容:

    • 首先,正如@GMan 所说,您对MakeCallback 的论证是不可演绎的。
    • 其次,MakeCallback 的返回类型错误。它应该是CallbackPtr,因为CallbackBasePtr typedef 不存在。这会导致 SFINAE 介入,并且即使在参数固定时也不将您的函数视为可能调用的函数。
    • 第三,你的FunctionCallback 构造函数需要一个funccb* 指针,而funccb 已经是一个(函数)指针,所以你必须传递一个指针-function-pointer,例如。 new FunctionCallback(&amp;cb)

    【讨论】:

    • 第二和第三,我明白(愚蠢的复制和粘贴),但你能详细说明一下吗?为什么论证不可演绎?类型应该是一样的吧?
    【解决方案3】:

    使用&lt;functional&gt; 比重新发明它更好……直接从编译器的实现中复制也更好。

    一般来说,使用更少的模板参数也是一件好事。

    但是,解决这些问题总是很诱人……所以,知道我在做什么,但现在不直接看,这就是我的处理方式。

    Call 一个函子的代码不会专门用于不同类型的函子,所以它应该在一般模板情况下。

    要对通用模板进行细微调整,最好使用特征类。

    template< typename F, typename = void >
    struct callback_traits {
        typedef F const &local_type; // fallback case: only keep a reference
    };
    
    template< typename F >
    struct callback_traits< F,
        typename std::enable_if< // GCC 4.4 missing is_copy_constructible:
            std::is_constructible< F, F const& >::value
        >::type > {
        typedef F local_type; // better to keep a copy as this is a callback
    };
    
    template< typename F >
    struct Callback {
        typedef typename callback_traits< F >::local_type local_type;
        local_type fn;
    
        Callback( local_type const &fn_in ) : fn( fn_in ) {}
        template< typename ... Args >
        typename std::result_of< local_type( Args ... ) >::type
        Call( Args ... a )
            { return fn( a ... ); }
    };
    

    【讨论】:

    • result_of 之后添加了一个缺失的(我认为)type,我对自己不太确定,所以如果您交叉检查,我将不胜感激。
    • @Matthieu:是的,谢谢!我之前根本没有测试过,但是通过这个修复它可以工作:ideone.com/KkX9C - 我还添加了对调用 printf 的支持。
    • 我知道我可以使用 functional,但我想看看关于可变参数模板的所有炒作 :) 顺便说一句,我喜欢你的解决方案,但你将如何扩展它以获得指向成员的指针方法,以及指向常量成员方法的指针?
    • @VJo:嗯,在functional 中,他们有很多不同的专业……请参阅更新,我对其进行了重构以区分关键案例,因此应该减少跑腿工作。新演示:ideone.com/IQghG
    【解决方案4】:

    修复了一些类型-o 并专门 MakeCallback 以接受函数指针。正如 GMan 所说,您对 MakeCallback 的模板参数是在不可演绎的上下文中。

    #include <memory>
    
    template< typename R, typename ... Args >
    class CallbackBase
    {
    public:
        typedef std::shared_ptr< CallbackBase< R, Args... > >
                CallbackPtr;
    
        virtual ~CallbackBase()
        {
        }
        virtual R Call(  Args ... args) = 0;
    };
    
    template< typename R, typename ... Args >
    class FunctionCallback : public CallbackBase< R, Args... >
    {
    public:
        typedef R (*funccb)(Args...);
    
        FunctionCallback( funccb  cb_ ) : 
            CallbackBase< R, Args... >(),
            cb( cb_ )
        {
        }
        virtual ~FunctionCallback()
        {
        }
        virtual R Call(Args... args)
        {
          return cb( args... );
        }
    private:
      funccb cb;
    };
    
    template < typename R, typename ...Args >
    typename CallbackBase< R, Args... >::CallbackPtr
        MakeCallback( R (*cb)(Args...)  )
    {
        typename CallbackBase< R, Args... >::CallbackPtr
            p( new FunctionCallback< R, Args... >( cb )
    );
        return p;
    }
    
    bool Foo_1args( const int & t)
    {
        return true;
    }
    int main()
    {
        auto cbObj = MakeCallback( & Foo_1args );
    }
    

    更新:

    C++ 标准在 14.8.2.5 [temp.deduct.type] 第 5 - 6 段中定义了非推导上下文。那里有一个项目符号列表,我不会声称完全理解.我的标记是:

    任何时候你在你的后面看到“::” 模板参数,那个模板 参数在一个 非推导 上下文,意思是它必须是 在调用站点明确指定。

    【讨论】:

    • 可能想指出到底出了什么问题:FunctionCallback 的 ctor 想要一个 funccb * 指针,而 funccb 已经是一个指针,所以它需要一个指针对指针。第二件事是MakeCallback的返回类型,它是CallbackBasePtr,显然不存在,它是CallbackPtr
    • 与 GMan 相同的问题:MakeCallback 的声明与我的有何不同?我的使用相同的 typedef。
    • 我尝试了非推导上下文的非正式定义。
    猜你喜欢
    • 2016-09-14
    • 1970-01-01
    • 1970-01-01
    • 2011-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-14
    相关资源
    最近更新 更多