I have is_callable trait defined like this:
#ifndef IS_CALLABLE_HPP
#define IS_CALLABLE_HPP
#include
namespace is_callable_detail
{
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:
Note: Visual Studio 2015 Update 2 is broken, because it incorrectly deduces std::index_sequence 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?
decltype surrounds the top-level expression, just like std::declval() . 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_asserts).
Without further ado, here is your standalone solution (although you might wish you hadn't asked):
#include
#include
namespace detail {
//template_worm CANNOT be used in evaluated contexts
struct template_worm {
template
operator T& () const;
template
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
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 \
constexpr inline auto \
__VA_ARGS__ (template_worm, T&&) -> template_worm { \
return template_worm{}; \
} \
\
template \
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
using worm_arg = template_worm const &;
template
struct success {};
struct substitution_failure {};
template
struct invoke_test {
template
auto operator()(T&& t, Rgs&&... rgs) const ->
success()(std::forward(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
struct force_template_test {
template
auto operator()(T&& t) const ->
success().template operator()())>;
auto operator()(...) const->substitution_failure;
};
template
struct try_invoke {
using test_1 = invoke_test;
using invoke_result = decltype(test_1{}(
::std::declval(),
::std::declval()...
));
using test_2 = force_template_test;
using force_template_result = decltype(test_2{}(std::declval()));
static constexpr bool value =
!std::is_same::value
|| !std::is_same::value;
static constexpr int arg_count = test_1::arg_count;
};
template
struct try_invoke {
using test = invoke_test;
using result = decltype(test{}(::std::declval()));
static constexpr bool value = !std::is_same::value;
static constexpr int arg_count = test::arg_count;
};
template
struct min_args;
struct sentinel {};
template
struct min_args {
static constexpr bool value = true;
static constexpr int arg_count = -1;
};
template
struct min_args> {
using next = typename std::conditional<
sizeof...(I)+1 <= Max,
std::make_index_sequence,
sentinel
>::type;
using result_type = std::disjunction<
try_invoke...>,
min_args
>;
static constexpr bool value = result_type::value;
static constexpr int arg_count = result_type::arg_count;
};
template
struct min_args {
using result_type = std::disjunction<
try_invoke,
min_args>
>;
static constexpr int arg_count = result_type::arg_count;
static constexpr bool value = result_type::value;
};
template
using min_arity = std::integral_constant::arg_count>;
}
// Here you go.
template
using is_callable = std::integral_constant::value >= 0>;
// This matches OP's first example.
struct Test1 {
template
T operator()() {
return{};
}
};
// Yup, it's "callable", at least by OP's definition...
static_assert(is_callable::value, "");
// This matches OP's second example.
struct Test2 {
template
auto operator()() -> decltype(std::declval() + std::declval()) {
return{};
}
};
// Yup, it's "callable", at least by OP's definition...
static_assert(is_callable::value, "");
// ints aren't callable, of course
static_assert(!is_callable::value, "");
int main() {}