首页 > 解决方案 > 如何为 C++ 重载解析和 SFINAE 编写正/负测试?

问题描述

我目前正在设计一些利用 SFINAE 来控制重载分辨率的函数。这很容易出错,所以我希望能够为可以调用 API 的内容编写正面和负面的测试。例如:

// A templated function that should only be used via type deduction. (We
// don't want to let users to set template arguments explicitly in order
// to reserve the right to change them. They are not a public API.)
template <
    int&... ExplicitArgumentBarrier,
    typename T,
    typename = std::enable_if<!std::is_same_v<T, int>>>
void AcceptVector(std::vector<T>);

// We should be able to feed the function most vectors.
static_assert(kCanCallAcceptVector<std::vector<double>>);
static_assert(kCanCallAcceptVector<std::vector<std::string>>);

// But not vector<int>.
static_assert(!kCanCallAcceptVector<std::vector<int>>);

感觉我应该可以用std::is_invocable_v. 相反,我真的想测试重载解决方案:无论有多少重载以及它们的模板参数是什么样的,提供特定类型作为参数AcceptVector的调用站点是否有效?AcceptVector

最好的方法是什么?这是我想出的:

#define DEFINE_CAN_CALL_VARIABLE(namespace_name, function_name)           \
  template <typename... Args>                                             \
  struct internal_CanCall##function_name final {                          \
   private:                                                               \
    struct Functor {                                                      \
      template <typename... U,                                            \
                typename = decltype(::namespace_name ::function_name(     \
                    std::declval<U>()...))>                               \
      void operator()(U&&... args);                                       \
    };                                                                    \
                                                                          \
   public:                                                                \
    static constexpr bool kValue = std::is_invocable_v<Functor, Args...>; \
  };                                                                      \
                                                                          \
  template <typename... Args>                                             \
  inline constexpr bool kCanCall##function_name =                         \
      internal_CanCall##function_name<Args...>::kValue;

DEFINE_CAN_CALL_VARIABLE(my::project, AcceptVector);

这个想法是定义一个我们可以命名的函子,std::is_invocable_v然后依靠该函子中的重载决议。但也有一些缺点:

这似乎是正确的吗?有没有更糟糕的方法来做到这一点?

标签: c++c++17c++20sfinaeoverload-resolution

解决方案


由于您标记了此 C++20,因此更好的方法是编写一个概念:

template <typename... Args>
concept canAcceptVector = requires (Args(*args)()...) {
    AcceptVector(args()...);
};

然后您可以测试:

// We should be able to feed the function most vectors.
static_assert(canAcceptVector<std::vector<double>>);
static_assert(canAcceptVector<std::vector<std::string>>);

// But not vector<int>.
static_assert(!canAcceptVector<std::vector<int>>);

函数指针的奇怪公式是确保canAcceptVector<int>canAcceptVector<int&>canAcceptVector<int&&>尝试AcceptVector分别使用纯右值、左值和 xvalue 调用。在这种情况下可能不是超级重要,但我发现它比处理std::forward和正确处理prvalues稍微不那么笨拙。


对于这个问题,最好的 C++17 方法是检测习语,它需要额外的概念:

template <typename... Args>
using AcceptVector_t = decltype(AcceptVector(std::declval<Args>()...));

template <typename... Args>
inline constexpr bool canAcceptVector = is_detected_v<AcceptVector_t, Args...>;

您可以static_assert以与上述相同的方式。


推荐阅读