如何为 C++ 重载解析和 SFINAE 编写 positive/negative 测试?
How can I write positive/negative tests for C++ overload resolution and 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
命名的仿函数,然后依靠该仿函数中的重载解析。但也有一些缺点:
我必须将它打包成一个宏,以使其可以重复使用,而不会再次出现与 std::is_invocable_v
相同的问题。
我需要让用户将命名空间作为宏参数提供,以避免因参数相关查找而出现问题。 (虽然我说我对任何拼写为 AcceptVector
的调用感兴趣,但我想我的意思是任何拼写为 AcceptVector
的调用解析为我项目命名空间中的 AcceptVector
函数。)
我不是 100% 确定我没有错过其他边缘情况。
这样看起来对吗?有没有更简单的方法来做到这一点?
既然你标记了这个 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&&>
尝试使用纯右值、左值和 xvalue 调用 AcceptVector
分别。在这种情况下可能不是特别重要,但我发现它比处理 std::forward
稍微不那么笨拙,而且它正确地处理 prvalues.
解决此问题的最佳 C++17 方法是 detection idiom,它需要对概念进行额外的步骤:
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
和上面一样
我目前正在设计一些使用 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
命名的仿函数,然后依靠该仿函数中的重载解析。但也有一些缺点:
我必须将它打包成一个宏,以使其可以重复使用,而不会再次出现与
std::is_invocable_v
相同的问题。我需要让用户将命名空间作为宏参数提供,以避免因参数相关查找而出现问题。 (虽然我说我对任何拼写为
AcceptVector
的调用感兴趣,但我想我的意思是任何拼写为AcceptVector
的调用解析为我项目命名空间中的AcceptVector
函数。)我不是 100% 确定我没有错过其他边缘情况。
这样看起来对吗?有没有更简单的方法来做到这一点?
既然你标记了这个 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&&>
尝试使用纯右值、左值和 xvalue 调用 AcceptVector
分别。在这种情况下可能不是特别重要,但我发现它比处理 std::forward
稍微不那么笨拙,而且它正确地处理 prvalues.
解决此问题的最佳 C++17 方法是 detection idiom,它需要对概念进行额外的步骤:
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
和上面一样