You are trying to detect an operator()
member function template with non-deduced template parameters, which isn't really "callable" at all, and also is kind of pointless - the function template should instead have a real name, because your example really misses the point of the whole operator
thing. But let's solve your problem anyway.
Allow me to preface this with a plug for a library solution I'm working on, called CallableTraits (again, a work in progress).
While your case is not handled by CallableTraits, the library does employ a technique I'm about to describe to solve a very similar problem. The technique is a total hack, but it's standard compliant, and works for me on the following platforms:
- GCC 5.2 and later
- Clang 3.5 and later
- Visual Studio 2015 Update 1 - basically works
Note: Visual Studio 2015 Update 2 is broken, because it incorrectly deduces std::index_sequence<I...>
in partial specializations... I filed a bug report. See here for a description.
Note: If your standard library implementation doesn't have std::disjunction
yet then you can use the sample implementation here instead.
I call the technique the template worm. It's the metaprogramming equivalent of spitting into a deep, dark well, just to hear how long it takes to splash.
What is a template worm?
- A template worm is a class that is convertible to anything and everything.
- Any operator expressions with a template worm operand will always evaluate to another template worm.
- A template worm can only be used in an unevaluated context. In other words, you can only use it when a
decltype
surrounds the top-level expression, just like std::declval<T>()
.
A template worm wiggles itself into places it isn't supposed to be, and sticks to the first concrete type it can find. In a similar fashion, a real worm will stick to the concrete on any afternoon in July.
To solve your problem, we will start with no arguments, then recursively work up to an arbitrary limit of 10. We try to make the call to the (potential) function object by trying to pass the template worm by by function-style invocation, AND by template type argument (per your requirements).
This code doesn't account for INVOKE semantics, because that takes signifcantly more code. If you need this to work with pointers-to-member-functions and pointers-to-member-data, you can roll your own implementation for that.
I might not have covered all the operators, and I might not have implemented them all correctly, but you'll see the point.
One last thing:
I know of one catch. The return type can't depend on a dependent name (other than member operators).
Edit: Also, invocation/template instantiation needs to be SFINAE-friendly (i.e. no static_assert
s).
Without further ado, here is your standalone solution (although you might wish you hadn't asked):
#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() {}