如何根据参数的调用操作符args或存在来重载模板函数?

Kla*_*aim 6 c++ templates sfinae variadic-templates c++11

注意:我正在使用VS2013,因此可用的C++ 11功能有限.

我在重载模板函数时遇到问题,具体取决于参数类型是否可调用,理想情况下,参数是否与特定模式匹配.

这是我的代码的一个非常简化的示例,我的问题是如何实现update_callabe()重载:

template< class T, class... Args >
void update_callable( const std::vector<T>& objects, Args&&... args ); // 1: How to implement this?

template< class T, class... UpdateArgs>
class Controller
{ //...
    virtual void update( T&, UpdateArgs... args ) = 0;
public:
    template< class IterBegin, class IterEnd, class... Args  >
    void update_batch( IterBegin first, IterEnd last, Args&&... args )
    {
        std::for_each( first, last, [&]( T& object ){ update(object, args...); }
    }
    //...
};

template< class T, class... UpdateArgs >
class Group
{
public:
    using ControllerType = Controller<T,UpdateArgs...>;

    void add( ControllerType& controler ) { /* ... */ m_controllers.emplace_back( &controller ); }

    template< class... Args >
    void update( Args&&... args )
    {
        update_callable(m_objects, std::forward<Args>(args)); // 2
        for( auto* controller : m_controllers )
        {
            controller->update_batch( begin(m_objects), end(m_objects), std::forward<Args>(args)); // 3
        }
    }

private:
    std::vector<T> m_objects;
    std::vector<ControllerType*> m_controllers;
    //...
};
Run Code Online (Sandbox Code Playgroud)

A. 我想通过update_callabe()重载实现的目标(按优先级顺序):

  1. 如果T可以使用Args参数调用,则使用参数调用所有T对象.
  2. 如果T不能用Args参数调用,那么什么都不做.

B.这对我来说没问题,但理想情况下我想要update_callabe()遵循这些规则的重载(优先顺序):

  1. 如果T可以使用Args参数调用,则使用参数调用所有T对象.
  2. 如果T可以使用NO参数调用,则调用所有没有参数的T对象.
  3. 如果T不能用Args参数调用,也不能用NO参数调用,那么什么都不做.

我尝试过enable_if,条件和几种高级技术,但我不是专家(还),所以我没有正确地表达这一点.

关于这个例子的一些注释:

  • 这是一个简化的例子,我没有尝试编译它,但它接近我的代码;
  • (2)基本上我们想调用存储的对象的默认更新,如果对象的类型提供一个,"默认更新"在这里意味着调用操作符要么使用来自更新上下文的参数,要么没有参数如果类型没有需要他们;
  • (3)"控制器"对象有第二个更新循环,可以在外部操纵存储的对象;

asc*_*ler 3

当我在编译时想要类似if//else ifelse行为时,我使用这样的技巧:

template <unsigned int N>
struct priority_helper
    : public priority_helper<N-1> {};

template <>
struct priority_helper<0U> {};

template <unsigned int N>
using priority = int priority_helper<N>::*;

constexpr priority<0> by_priority{};

template <typename Arg>
auto do_thing_detail(Arg&& arg, priority<1>)
    -> typename std::enable_if<cond1<Arg>::value>::type
{ /*...*/ }
template <typename Arg>
auto do_thing_detail(Arg&& arg, priority<2>)
    -> typename std::enable_if<cond2<Arg>::value>::type
{ /*...*/ }
template <typename Arg>
void do_thing_detail(Arg&& arg, priority<3>)
{ /*...*/ }

template <typename Arg>
void do_thing(Arg&& arg)
{ do_thing_detail(std::forward<Arg>(arg), by_priority); }
Run Code Online (Sandbox Code Playgroud)

priority_helper<N>*使用更简单的类型而不是也可以使用int priority_helper<N>::*,但更大的值N将是首选重载,因为派生指针比基指针更具体。通过使用指向成员的指针,隐式转换和重载首选项会以相反的方式进行(指向基类成员的指针转换为指向派生成员的指针)。

所以对于你的问题,按照priority<N>上面的定义之后......

template < class T, class... Args >
auto update_callable_detail(
    priority<1>,
    const std::vector<T>& objects,
    Args&& ... args )
    -> decltype(std::declval<const T&>()(std::forward<Args>(args)...), void())
{
    for ( const T& obj : objects )
        obj( std::forward<Args>(args)... );
}

template < class T, class... Args >
auto update_callable_detail(
    priority<2>,
    const std::vector<T>& objects,
    Args&& ... )
    -> decltype(std::declval<const T&>()(), void())
{
    for ( const T& obj : objects )
        obj();
}

template < class T, class... Args >
void update_callable_detail(
    priority<3>,
    const std::vector<T>&,
    Args&& ... )
{
}

template < class T, class... Args >
void update_callable( const std::vector<T>& objects, Args&& ... args )
{
    update_callable_detail( by_priority, objects, std::forward<Args>(args)... );
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,直接在重载声明中使用 SFINAE 似乎更简单,而不是做任何事情std::result_of(特别是因为 C++11 的要求result_of不如 C++14 版本那么有用)。T每当和 的推导参数导致Args中的非法表达式时decltype,该重载就会在重载决策期间被抛出。