如何在模板参数中将std :: result_of转换为decltype

时间:2016-09-23 10:08:56

标签: c++ c++11 c++14 decltype

在CppCon 2015上,来自Microsoft {3}的S. Lavavej避免使用result_of。但我的情况似乎无法找到合适的替代方案。

请考虑以下代码。有没有办法改变std::result_of<F()>::type来改为使用decltype

#include <iostream>
#include <future>
#include <type_traits>

template<typename T>
struct NopErrCB
{
  constexpr T operator() () const { return T(); }
};

template <typename Func, typename ErrCB>
struct SafeTaskWrapper {
  Func f;
  ErrCB errCB;

  template <typename... T>
  auto operator()(T&&... args) -> decltype(f(args...)) {
    try
    {
      return f(std::forward<T>(args)...);
    }
    catch (...)
    {
      return errCB();
    }
  }

};
//                                   vvv OVER HERE  vvv
template <typename F>
SafeTaskWrapper<F, NopErrCB<typename std::result_of<F()>::type>> make_safe_task(F&& f) {
  return { std::forward<F>(f) };
}

template <typename F, typename ErrCB>
SafeTaskWrapper<F, ErrCB> make_safe_task(F&& f, ErrCB&& cb) {
  return { std::forward<F>(f), std::forward<ErrCB>(cb) };
}

int main() {
  auto futA = std::async(std::launch::async, make_safe_task([] { throw "A"; return 1; }));
  auto futB = std::async(std::launch::async, make_safe_task([] { throw "B"; return 1; }, [] { return 2; }));
  auto futC = std::async(std::launch::async, make_safe_task([] { throw "C"; }));
  std::cout << futA.get() << std::endl;
  std::cout << futB.get() << std::endl;
  futC.get();
}

<子> PS。请不要介意SafeTaskWrapper的目的。我知道未来已经处理了C ++异常。这只是一个演示,实际代码用于处理Windows SEH异常,但这对于这个问题并不重要。

4 个答案:

答案 0 :(得分:4)

您可以使用

decltype(std::declval<F>()())

而不是

typename std::result_of<F()>::type

相似但有细微差别

答案 1 :(得分:4)

是的,您在这里使用result_of会导致错误。是的,有一种方法可以编写没有result_of的代码来清除错误。我们可以做到这一点,而无需构建一个等效的错误decltype替换。

首先,请注意您的result_of代码有错误。

struct evil {
  template<class...Args>
  int operator()(Args&&)&&{return 0;}
  template<class...Args>
  std::string operator()(Args&&)const&{return "hello";}
};

evil{}传递给您的代码,它将无法编译。这是因为您在左值上下文中评估了f中的SafeTaskWrapper,而您在右值上下文中计算了result_of

这正是S.T.L的那种错误。正在谈论。

现在,std::result_of_t<F(Args...)>基本上 1 等同于decltype( std::declval<F>()( std::declval<Args>()... ) ),或者在这种情况下decltype( std::declval<F>()())。在这个意义上用result_of_t替换decltype是完全没用的。

我在这里做的是改变你的其余代码。

struct anything_really {
  template<class T> operator T() const { return {}; }
};
struct return_anything {
  anything_really operator()() const { return {}; }
};

这将取代您的errCB类型。请注意,它不是模板类型。

接下来,我们必须处理“不要回归无效”的问题。问题。这样做:

template<class R>
struct smart_invoke {
  template<class F>
  R operator()(F&& f)const {
    return std::forward<F>(f)();
  }
};
template<>
struct smart_invoke<void> {
  template<class F>
  void operator()(F&& f)const {
    std::forward<F>(f)();
  }
};

这为我们提供了最终解决方案:

template <typename Func, typename ErrCB=return_anything>
struct SafeTaskWrapper {
  Func f;
  ErrCB errCB;

  template <class... Ts>
  decltype(auto) operator()(Ts&&... args) {
    try {
      return f(std::forward<T>(args)...);
    } catch (...) {
      using R=decltype( f( std::forward<T>(args)... ) );
      return smart_invoke<R>{}(errCB);
    }
  }
};

现在我们的工厂正常运作。我可以保留两个,但我很懒。

template <class ErrCB=return_anything, class F>
SafeTaskWrapper<F, ErrCB> make_safe_task(F&& f, ErrCB&& cb = {}) {
  return { std::forward<F>(f), std::forward<ErrCB>(cb) };
}

我还交换了模板参数顺序,因此你可以传入一个不同的default-constructible ErrCB而不用在我使用时传入一个值。

result_of_t已被淘汰。

你的尾随返回decltype也有错误。它应该完全与(key)return语句相同,不相似。

1 如果F是成员函数指针或成员等,则存在例外情况,但无论如何您的代码都无法在这些情况下工作。在C ++ 17中,您还希望在std::invokeoperator()返回类型子句中调用decltype。其他更改仍然正确,因为我们避免在两个位置推断返回类型,所以它们保持不变。

答案 2 :(得分:2)

使用std::result_of的地方。我认为这就足够了。

如果您想要更短且更易读的代码,请考虑使用自{+ 1}以来添加的std::result_of_t

//NopErrCB<typename std::result_of<F()>::type>
NopErrCB<std::result_of_t<F()>> // equal

答案 3 :(得分:1)

直接翻译将是:

template <typename F>
SafeTaskWrapper<F, NopErrCB<decltype(std::declval<F>()())>> make_safe_task(F&& f) {
  return { std::forward<F>(f) };
}

对我来说这似乎并不清楚。