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

时间:2014-06-30 17:05:35

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

注意:我正在使用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;
    //...
};

一个。 我希望通过update_callabe()重载(按优先级顺序)实现

  1. 如果T可以使用Args参数调用,则使用参数调用所有T对象。
  2. 如果T不能用Args参数调用,那么什么都不做。
  3. B中。这对我来说没问题,但理想情况下我希望update_callabe()超载遵循这些规则(按优先顺序排列)

    1. 如果T可以使用Args参数调用,则使用参数调用所有T对象。
    2. 如果T可以使用NO参数调用,则调用所有没有参数的T对象。
    3. 如果T不能用Args参数调用,也不能用NO参数调用,那么就什么都不做。
    4. 我尝试过enable_if,条件和一些高级技术,但我还没有专家(还),所以我没有正确表达这一点。

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

      • 这是一个简单的例子,我没有尝试编译它,但它接近我的代码;
      • (2)基本上我们想要调用存储对象的默认更新,如果对象的类型提供了一个,&#34;默认更新&#34;这里的调用操作符是来自更新上下文的参数,如果类型不需要参数,则不带参数;
      • (3)&#34; controller&#34;有第二个更新循环。可以在外部操纵存储对象的对象;

3 个答案:

答案 0 :(得分:3)

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

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); }

这也可以使用更简单的类型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)... );
}

在这种情况下,直接在过载声明中使用SFINAE似乎更简单,而不是对std::result_of执行任何操作(特别是因为result_of的C ++ 11要求不是有用的C ++ 14版本)。每当TArgs的推断参数导致decltype中的非法表达式时,在重载解析期间就会抛出该重载。

答案 1 :(得分:1)

可以使用一些特征和一些标签调度(Demo at Coliru)。首先,定义确定T是否可以使用指定的参数类型调用的特征:

template <typename T, typename... Args>
struct callable_with_args_ {
    template <typename U=T>
    static auto test(int) ->
      decltype((void)std::declval<U>()(std::declval<Args>()...), std::true_type());
    static auto test(...) -> std::false_type;
    using type = decltype(test(0));
};

template <typename T, typename... Args>
using callable_with_args = typename callable_with_args_<T, Args...>::type;

或没有参数:

template <typename T>
struct callable_without_args_ {
    template <typename U=T>
    static auto test(int) ->
      decltype((void)std::declval<U>()(), std::true_type());
    static auto test(...) -> std::false_type;
    using type = decltype(test(0));
};

template <typename T>
using callable_without_args = typename callable_without_args_<T>::type;

然后实现两级标记调度以获得所需的优先级:

template < class T >
void update_callable_no_args(std::false_type, const std::vector<T>&) {}

template < class T >
void update_callable_no_args(std::true_type, const std::vector<T>& objects) {
    for (auto&& i : objects) {
        i();
    }
}

template< class T, class... Args >
void update_callable_args(std::false_type,
                          const std::vector<T>& objects,
                          Args&&... ) {
    update_callable_no_args(callable_without_args<T const&>(), objects);
}

template< class T, class... Args >
void update_callable_args(std::true_type,
                          const std::vector<T>& objects,
                          Args&&... args ) {
    for (auto&& i : objects) {
        i(args...);
    }
}

template< class T, class... Args >
void update_callable( const std::vector<T>& objects, Args&&... args ) {
    using callable = callable_with_args<
      T const&, typename std::add_lvalue_reference<Args>::type...
    >;
    update_callable_args(callable(), objects, std::forward<Args>(args)...);
}

请注意,我将参数类型强制为左值引用,以避免让任何可调用的“吃掉”右值引用参数导致后来的callables看到移动的对象。如果您想要可以移动rvalues,请删除update_callable中的强制:

using callable = callable_with_args<
  T const&, Args&&...
>;

并将它们转发给update_callable_args中的每个可调用对象:

for (auto&& i : objects) {
  i(std::forward<Args>(args)...);
}

VS2013 at least seems to compile it properly

答案 2 :(得分:0)

这是使用表达式SFINAE的部分解决方案(不确定您的VC版本是否支持)

template<class T>
auto update_callable(const std::vector<T>&, ...) 
    -> void
{ 
}

template< class T, class... Args>
auto update_callable( const std::vector<T>& objects, Args&&... args) 
    -> decltype(std::declval<T>()(std::forward<Args>(args)...))
{ 
    for (auto&& elem : objects)
        elem(std::forward<Args>(args)...);
}

template< class T, class... Args>
auto update_callable( const std::vector<T>& objects, Args&&... args) 
    -> decltype(std::declval<T>()())
{ 
    for (auto&& elem : objects)
        elem();
}

它是一个部分解决方案,因为第一个重载的varargs ...参数只支持POD参数(这就是我粘贴在std::vector<T>参数中的原因,因为向量不是POD )。

你可以这样测试:

struct N {};

struct Z 
{ 
    void operator()() const
    { std::cout << "Z(), "; }
};

struct T
{   
    void operator()(int x, int y) const
    { std::cout << "T(," << x << "," << y << "), "; }
};

int main()
{
    auto vN = std::vector<N>(1); 
    auto vZ = std::vector<Z>(2); 
    auto vT = std::vector<T>(3); 

    update_callable(vN, 1, 2);std::cout << "end of 1st\n";
    update_callable(vZ, 1, 2);std::cout << "end of 2nd\n";
    update_callable(vT, 1, 2);std::cout << "end of 3rd\n";
}

Live Example输出

  

第1次结束

     

Z(),Z(),第2次结束

     

T(1,2),T(1,2),T(1,2),第3次结束