我可以根据其operator()的签名来专门设计可变参数模板参数吗?

时间:2018-10-05 07:34:28

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

假设我有这样的功能

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()的任何东西一起使用。

我可以直接编写合适的专业化知识吗,还是必须对traits类和SFINAE做一些奇怪的事情?

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

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)... )
  {}
};
我认为这足以满足我们的目的。 (更一般而言,您可以制作一个二叉树(是否平衡),并处理完美的转发,等等。