【问题标题】:Can I specialize a variadic template argument based on the signature of its operator()我可以根据其 operator() 的签名专门化可变参数模板参数吗
【发布时间】:2019-03-10 15:52:11
【问题描述】:

假设我有这样的功能

template <typename... FunctionList>
void call_all (int i, float f, const FunctionList... function_list);

template <>
void call_all (int, float)
{
}

我想像这样专门化它:

template <typename HasIntArgument, typename... FL>
void call_all (int i, float f, const HasIntArgument & hia, const FL... list)
{
    hia (i);
    call_all (i, f, list...);
}

template <typename HasFloatArgument, typename... FL>
void call_all (int i, float f, const HasFloatArgument & hfa, const FL... list)
{
    hfa (f);
    call_all (i, f, list...);
}

换句话说,我希望这个函数对于function_list 中的每个类函数对象,确定它是否可以用签名void(int)void(float) 调用。 (此列表中的任何内容都不能使用多个签名进行调用。)

我希望它可以与原始函数指针、lambda 或任何具有合适 operator() 的东西一起使用。

我可以直接写一个合适的专业,还是我必须用特质类和 SFINAE 做一些奇怪的事情?

【问题讨论】:

  • 你根本不能部分特化函数模板,你想要重载,而不是特化。

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


【解决方案1】:

你可能会这样做:

#if 0 // C++17
template <typename F>
void dispatch(F func, int i, float f)
{
    if constexpr (has_int_argument<F>::value) {
        func(i);
    } else {
        func(f);
    }
}
#else // C++11
template <typename F>
typename std::enable_if<has_int_argument<F>::value>::type
dispatch(F func, int i, float)
{
    func(i);
}
template <typename F>
typename std::enable_if<!has_int_argument<F>::value>::type
dispatch(F func, int, float f)
{
    func(f);
}
#endif    


template <typename... Fs>
void call_all (int i, float f, const Fs&... fs)
{
    // (dispatch(fs, i, f), ...); // C++17

    const int dummy[] = {0, (dispatch(fs, i, f), 0)...};
    static_cast<void>(dummy);
}

具有适当的功能特征has_int_argument。类似:

template <typename ClassOrSig> struct funct_trait;

template <typename C>
struct funct_trait : funct_trait<decltype(&C::operator())> {};

template <typename C, typename Ret, typename ...Args>
struct funct_trait<Ret (C::*) (Args...)> : funct_trait<Ret(Args...)> {};

template <typename C, typename Ret, typename ...Args>
struct funct_trait<Ret (C::*) (Args...) const> : funct_trait<Ret(Args...)> {};

// &&, &, volatile, ... (C ellipsis)

template <typename Ret, typename ...Args>
struct funct_trait<Ret (*)(Args...)> : funct_trait<Ret(Args...)> {};

template <typename Ret, typename ...Args>
struct funct_trait<Ret (Args...)>
{
    using sig_type = Ret(Args...);
    using args_tuple = std::tuple<Args...>;
    // ...
};

template <typename T>
using has_int_argument = std::is_same<std::tuple<int>,
                                      typename funct_trait<T>::args_tuple>;

Demo

【讨论】:

    【解决方案2】:
    template<class...Fs>struct overloaded:Fs...{
      using Fs::operator()...;
    };
    template<class...Fs>
    overloaded(Fs...)->overloaded<Fs...>;
    

    以上在 中有点棘手,但到处都有实现。

    namespace details {
      struct secret_tag {};
      struct secret_result {
        template<class...Ts>
        secret_tag operator()(Ts&&...) const;
      };
      template<class F>
      using secret_tester = overloaded<std::decay_t<F>, secret_result>;
    }      
    template<class F, class Arg>
    using match_arg_exactly = std::integral_constant<
      bool,
      !std::is_same<
        details::secret_tag, 
        std::result_of_t< details::secret_tester<F>(Arg) >
      >{}
    >;
    

    现在我们可以请求一个给定的对象,如果它可以完全匹配一个特定的参数。

    template <typename HasIntArgument>
    void call_one(int i, float f, std::true_type, const HasIntArgument & hia)
    {
      hia (i);
    }
    template <typename HasFloatArgument>
    void call_one(int i, float f, std::false_type, const HasFloatArgument& hia)
    {
      hia (f);
    }
    template <typename F>
    void call_one(int i, float f, const F & hia)
    {
      call_one( i, f, match_arg_exactly<const F&, int>{}, hia );
    }
    

    我们使用这个:

    void call_all (int, float)
    {}
    
    template<class F, class...Fs>
    void call_all (int i, float f, F const& f0, Fs const&...fs) {
      call_one( i, f, f0 );
      call_all(i, f, fs...); 
    }
    

    测试代码:

    struct float_eater {
      void operator()(float x)const{ std::cout<< "float "<<x<<"\n"; }
    };
    struct int_eater {
      void operator()(int x)const{ std::cout<< "int "<<x<<"\n"; }
    };
    
    call_all( 42, 3.14, float_eater{}, int_eater{}, int_eater{} );
    

    Live example

    overloaded 类似于:

    template<class...Fs>
    struct overloaded;
    
    template<class F0>
    struct overloaded<F0>:F0 {
      overloaded(F0 f0):F0(std::move(f0)) {}
      using F0::operator();
    };
    
    template<class F0, class F1>
    struct overloaded<F0, F1>: F0, F1 {
      overloaded( F0 f0, F1 f1 ):F0(std::move(f0)), F1(std::move(f1)) {}
      using F0::operator();
      using F1::operator();
    };
    
    template<class F0, class...Fs>
    struct overloaded<F0, Fs...>:
      overloaded<F0, overloaded<Fs...>>
    {
      overloaded(F0 f0, Fs...fs):
        F0(std::move(f0)),
        overloaded<Fs...>( std::move(fs)... )
      {}
    };
    

    我认为这足以满足我们的目的。 (更一般地说,你要么制作一棵平衡或不平衡的二叉树),然后处理完美转发,等等。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-31
      • 2017-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多