您正在尝试检测operator()
具有非推导模板参数的成员函数模板,这根本不是真正的“可调用”,而且有点毫无意义 - 函数模板应该有一个真实的名称,因为您的示例真的错过了整件事的重点operator
。但无论如何,让我们解决您的问题。
请允许我用一个我正在开发的库解决方案的插件作为序言,称为CallableTraits(同样,正在进行中的工作)。
虽然您的案例不是由 CallableTraits 处理的,但该库确实采用了我将要描述的一种技术来解决一个非常相似的问题。该技术完全是 hack,但它符合标准,并且适用于以下平台:
- GCC 5.2 及更高版本
- Clang 3.5 及更高版本
- Visual Studio 2015 Update 1 - 基本上可以工作
注意:Visual Studio 2015 Update 2 已损坏,因为它错误地推断出std::index_sequence<I...>
部分专业化......我提交了一个错误报告。有关说明,请参见此处。
注意:如果您的标准库实现还没有,那么您可以使用此处std::disjunction
的示例实现。
我称这种技术为模板蠕虫。这是一种元编程,相当于向一口又深又黑的井里吐口水,只是为了听听溅起需要多长时间。
什么是模板蠕虫?
- 模板蠕虫是一个可以转换为任何东西的类。
- 任何带有模板蠕虫操作数的运算符表达式将始终计算为另一个模板蠕虫。
- 模板蠕虫只能在未评估的上下文中使用。换句话说,您只能在 a
decltype
包围顶级表达式时使用它,就像std::declval<T>()
.
模板蠕虫会在不应该出现的地方摆动自己,并坚持它可以找到的第一个具体类型。以类似的方式,一条真正的蠕虫会在七月的任何一个下午粘在混凝土上。
为了解决您的问题,我们将从无参数开始,然后递归地工作到任意限制 10。我们尝试通过函数式调用传递模板蠕虫来调用(潜在)函数对象,并通过模板类型参数(根据您的要求)。
此代码不考虑INVOKE语义,因为这需要更多的代码。如果您需要它与指向成员函数的指针和指向成员数据的指针一起使用,您可以为此推出自己的实现。
我可能没有涵盖所有的运算符,也可能没有正确地实现它们,但你会明白这一点的。
最后一件事:
我知道一个捕获。返回类型不能依赖于依赖名称(成员运算符除外)。
编辑:另外,调用/模板实例化需要对 SFINAE 友好(即没有static_assert
s)。
事不宜迟,这是您的独立解决方案(尽管您可能希望自己没有问过):
#include <utility>
#include <type_traits>
namespace detail {
//template_worm CANNOT be used in evaluated contexts
struct template_worm {
template<typename T>
operator T& () const;
template<typename T>
operator T && () const;
template_worm() = default;
#ifndef _MSC_VER
// MSVC doesn't like this... because it can deduce void?
// Whatever, we can do without it on Windows
template<typename... T>
template_worm(T&&...);
#endif //_MSC_VER
template_worm operator+() const;
template_worm operator-() const;
template_worm operator*() const;
template_worm operator&() const;
template_worm operator!() const;
template_worm operator~() const;
template_worm operator()(...) const;
};
#define TEMPLATE_WORM_BINARY_OPERATOR(...) \
\
template<typename T> \
constexpr inline auto \
__VA_ARGS__ (template_worm, T&&) -> template_worm { \
return template_worm{}; \
} \
\
template<typename T> \
constexpr inline auto \
__VA_ARGS__ (T&&, template_worm) -> template_worm { \
return template_worm{}; \
} \
\
constexpr inline auto \
__VA_ARGS__ (template_worm, template_worm) -> template_worm { \
return template_worm{}; \
} \
/**/
TEMPLATE_WORM_BINARY_OPERATOR(operator+)
TEMPLATE_WORM_BINARY_OPERATOR(operator-)
TEMPLATE_WORM_BINARY_OPERATOR(operator/)
TEMPLATE_WORM_BINARY_OPERATOR(operator*)
TEMPLATE_WORM_BINARY_OPERATOR(operator==)
TEMPLATE_WORM_BINARY_OPERATOR(operator!=)
TEMPLATE_WORM_BINARY_OPERATOR(operator&&)
TEMPLATE_WORM_BINARY_OPERATOR(operator||)
TEMPLATE_WORM_BINARY_OPERATOR(operator|)
TEMPLATE_WORM_BINARY_OPERATOR(operator&)
TEMPLATE_WORM_BINARY_OPERATOR(operator%)
TEMPLATE_WORM_BINARY_OPERATOR(operator,)
TEMPLATE_WORM_BINARY_OPERATOR(operator<<)
TEMPLATE_WORM_BINARY_OPERATOR(operator>>)
TEMPLATE_WORM_BINARY_OPERATOR(operator<)
TEMPLATE_WORM_BINARY_OPERATOR(operator>)
template<std::size_t Ignored>
using worm_arg = template_worm const &;
template<typename T>
struct success {};
struct substitution_failure {};
template<typename F, typename... Args>
struct invoke_test {
template<typename T, typename... Rgs>
auto operator()(T&& t, Rgs&&... rgs) const ->
success<decltype(std::declval<T&&>()(std::forward<Rgs>(rgs)...))>;
auto operator()(...) const->substitution_failure;
static constexpr int arg_count = sizeof...(Args);
};
// force_template_test doesn't exist in my library
// solution - it exists to please OP
template<typename... Args>
struct force_template_test {
template<typename T>
auto operator()(T&& t) const ->
success<decltype(std::declval<T&&>().template operator()<Args...>())>;
auto operator()(...) const->substitution_failure;
};
template<typename T, typename... Args>
struct try_invoke {
using test_1 = invoke_test<T, Args...>;
using invoke_result = decltype(test_1{}(
::std::declval<T>(),
::std::declval<Args>()...
));
using test_2 = force_template_test<Args...>;
using force_template_result = decltype(test_2{}(std::declval<T>()));
static constexpr bool value =
!std::is_same<invoke_result, substitution_failure>::value
|| !std::is_same<force_template_result, substitution_failure>::value;
static constexpr int arg_count = test_1::arg_count;
};
template<typename T>
struct try_invoke<T, void> {
using test = invoke_test<T>;
using result = decltype(test{}(::std::declval<T>()));
static constexpr bool value = !std::is_same<result, substitution_failure>::value;
static constexpr int arg_count = test::arg_count;
};
template<typename U, std::size_t Max, typename = int>
struct min_args;
struct sentinel {};
template<typename U, std::size_t Max>
struct min_args<U, Max, sentinel> {
static constexpr bool value = true;
static constexpr int arg_count = -1;
};
template<typename U, std::size_t Max, std::size_t... I>
struct min_args<U, Max, std::index_sequence<I...>> {
using next = typename std::conditional<
sizeof...(I)+1 <= Max,
std::make_index_sequence<sizeof...(I)+1>,
sentinel
>::type;
using result_type = std::disjunction<
try_invoke<U, worm_arg<I>...>,
min_args<U, Max, next>
>;
static constexpr bool value = result_type::value;
static constexpr int arg_count = result_type::arg_count;
};
template<typename U, std::size_t Max>
struct min_args<U, Max, void> {
using result_type = std::disjunction<
try_invoke<U, void>,
min_args<U, Max, std::make_index_sequence<1>>
>;
static constexpr int arg_count = result_type::arg_count;
static constexpr bool value = result_type::value;
};
template<typename T, std::size_t SearchLimit>
using min_arity = std::integral_constant<int,
min_args<T, SearchLimit, void>::arg_count>;
}
// Here you go.
template<typename T>
using is_callable = std::integral_constant<bool,
detail::min_arity<T, 10>::value >= 0>;
// This matches OP's first example.
struct Test1 {
template<typename T>
T operator()() {
return{};
}
};
// Yup, it's "callable", at least by OP's definition...
static_assert(is_callable<Test1>::value, "");
// This matches OP's second example.
struct Test2 {
template<typename T, typename U>
auto operator()() -> decltype(std::declval<T>() + std::declval<U>()) {
return{};
}
};
// Yup, it's "callable", at least by OP's definition...
static_assert(is_callable<Test2>::value, "");
// ints aren't callable, of course
static_assert(!is_callable<int>::value, "");
int main() {}