具有特定签名的C ++重载模板

时间:2017-07-23 08:46:47

标签: c++ templates overloading

我有以下内容:

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<html ng-app = "appModule">
   <table ng-controller = "AppController">
    <thead>
        <tr>
            <th ng-repeat="head in heads">{{ head }}</th>
        </tr>
    </thead>
    <tbody>
        <tr ng-repeat="person in people">
            <td ng-repeat="attr in attrs"> {{ person[attr]}} </td>
        </tr>
    </tbody>
</table>

</html>

哪个好,我可以做到以下几点:

struct Args;

template <typename T>
void DoStuff(T&& callback) {
    // ... do stuff
    MyArgs args = ...
    callback(args);
}

void callback(const Args &);
DoStuff(callback);

DoStuff([](const Args &) { ... });

一切都很好,现在我想在这里进行两项改进:

  1. 强制class CallbackClass { operator()(const Args &); }; CallbackClass myCallbackClass; DoStuff(myCallbackClass); 的签名始终为T,因为在当前状态下,我可以执行以下操作:

    void(const Args &)
  2. 允许(除了之前的选项)传递一个具有特定命名成员函数的对象,该函数也可以作为回调,例如:

    void badCallback(Args);
    DoStuff(badCallback);
    
  3. 有可能吗?

2 个答案:

答案 0 :(得分:2)

简单方法

您可以使用std::function作为参数或原始指针来解决问题。

第一个版本(std::function)略胜一筹,因为允许使用对象仿函数

void DoStuff_fn(std::function<void(const Args&)> callback) { callback(Args{}); }

void DoStuff_raw(void (*callback)(const Args&)) { callback(Args{}); }

struct Functor {
  void operator()(const Args&) {}
};

// ...
DoStuff_fn(Functor{});      // right call operator() of object
// DoStuff_raw(Functor{});  // error no conversion available

开销(优化)非常小,您可以在演示程序集中看到。无论如何,如果您需要关于这些主题的一些高级答案,最好打开一个特定的问题。

关于你的观点:

  

强制T的签名始终为空(const Args&amp;),因为在当前状态下我可以执行以下操作:

void badCallback(Args);
DoStuff(badCallback);

Args可隐式转换为const Args&,因此您无法存档。无论如何不允许使用不良演员表(我已提交DoStuff_fn)。

void bad_callback(Args&) {}

// ...
// DoStuff_fn(bad_callback);  // error! cannot cast Args& --> const Args&

高级方法

问候你的问题:

  

允许(除了前面的选项之外)传递一个具有特定命名成员函数的对象,该函数也可以被允许作为回调

这是可能的,但有一些高级技巧。

我在这个问题中建议你采用一种非常简单的方法(利用 C ++ 17 constexpr if)。

如果您需要更强大的功能或 C ++ 11兼容,您应该利用SFINAEhere一个好的教程)。

相反,我的方法(保持这个问题非常简单易读):

using Signature = std::function<void(const Args&)>;
template <typename T>
void DoStuff_adv(T&& functor) {
  if constexpr (std::is_convertible<T, Signature>::value) {
    functor(Args{});
  } else {
    functor.perform_op(Args{});
  }
}

这样,T类型应该是带有签名void(const Args&)可转换的仿函数,否则它应该具有perform_op方法。

Here演示。

修改

如果您想利用 SFINAE ,您的方法应该是:

using Signature = std::function<void(const Args&)>;

template <typename T>
typename std::enable_if<std::is_convertible<T, Signature>::value>::type
DoStuff_adv(T&& functor) {
  functor(Args{});
}

template <typename T>
typename std::enable_if<has_special_method<T>::value>::type
DoStuff_adv(T&& functor) {
  functor.perfom_op(Args{});
}

提升解决方案 使用 Boost 库:

#include <boost/tti/has_member_function.hpp>

using Signature = std::function<void(const Args&)>;

template <typename T>
typename std::enable_if<std::is_convertible<T, Signature>::value>::type
DoStuff_adv(T&& functor) {
  functor(Args{});
}

BOOST_TTI_HAS_MEMBER_FUNCTION(perform_op)

template <typename T>
typename std::enable_if<
    has_member_function_perform_op<void (T::*)(const Args&)>::value>::type
DoStuff_adv(T&& functor) {
  functor.perform_op(Args{});
}

答案 1 :(得分:0)

你的第一个问题可以这样解决:

#include <type_traits>
#include <tuple>

// the generic signature falls back on the signature of the call operator if present
template <class C>
struct signature : signature< decltype( &std::decay_t<C>::operator() ) >  {};

// pointer to member function fall back on the plain function signatures
template < class C , typename Result , typename... Args >
struct signature< Result (C::*)(Args...) > : signature< Result ( Args... ) > {};

template < class C , typename Result , typename... Args >
struct signature< Result (C::*)(Args...) const > : signature< Result ( Args... ) > {};

// pointer and references to free function fall back on the plain function signatures
template < typename Result , typename... Args >
struct signature< Result (*)(Args...) > : signature< Result ( Args... ) > {};

template < typename Result , typename... Args >
struct signature< Result (&)(Args...) > : signature< Result ( Args... ) > {};

// actual implementation just for pure function signature types
template < typename Result , typename... Args >
struct signature< Result ( Args... ) >
{
   static constexpr auto num_args = sizeof...(Args);

   template< size_t n >
   using  argument = typename std::tuple_element< n, std::tuple<Args...> >;
   using  result_type = Result;
};

template <typename Callable, size_t N >
using argument_t = typename signature<Callable>::template argument<N>::type;


// -------------------------------------------

struct Args {};

template <typename T> // could use enable_if as well
                      // , typename = std::enable_if_t<std::is_same_v<argument_t<T,0>,const Args&>>>
void DoStuff(T&& callback) {
    static_assert(std::is_same_v<argument_t<T,0>,const Args&>, "Callback has the wrong signature");
    // ... do stuff
    Args args = {};
    callback(args);
}

void callback(const Args &) {}

struct CallbackClass {
    void operator()(const Args &){}
};


int main()
{
    DoStuff(callback);
    DoStuff(CallbackClass());
    DoStuff([](const Args &) {  });
    // DoStuff([](Args) {  });  // won't compile, triggers static assertion informing the user about the error.
}

DEMO

关于你可以使用成员函数检测技术解决的第二个问题。存在许多版本,例如herehere