Как написать наилучший возможный is_callable признак шаблона оператора()
У меня есть is_callable признак, подобный этому:
#ifndef IS_CALLABLE_HPP
#define IS_CALLABLE_HPP
#include <type_traits>
namespace is_callable_detail
{
struct no {};
struct yes { no x[2]; };
template<bool CallableArgs, typename Callable, typename ReturnType, typename ...Args>
struct check_return
{
static const bool value = std::is_convertible<decltype(std::declval<Callable>()(std::declval<Args>()...)), ReturnType>::value;
};
template<typename Callable, typename ReturnType, typename ...Args>
struct check_return<false, Callable, ReturnType, Args...>
{
static const bool value = false;
};
}
template<typename Callable, typename Function>
struct is_callable;
template<typename Callable, typename ReturnType, typename ...Args>
struct is_callable<Callable, ReturnType(Args...)>
{
private:
template<typename T>
static is_callable_detail::yes check(decltype(std::declval<T>()(std::declval<Args>()...)) *);
template<typename T>
static is_callable_detail::no check(...);
static const bool value_args = sizeof(check<Callable>(nullptr)) == sizeof(is_callable_detail::yes);
static const bool value_return = is_callable_detail::check_return<value_args, Callable, ReturnType, Args...>::value;
public:
static const bool value = value_args && value_return;
};
#endif // IS_CALLABLE_HPP
Мой вопрос заключается в том, как обнаружить шаблонный оператор(), который не имеет аргументов и имеет только тип возврата T
template<typename T>
T operator()()
{
// ...
}
или
template<typename T, typename U>
auto operator()() -> decltype(std::declval<T>() + std::declval<U>())
{
// ...
}
Я знаю, что эти ситуации встречаются редко, но я хотел спросить, есть ли способ обнаружить наличие шаблонного оператора() без аргументов и с одним или несколькими аргументами шаблона.
Ответы
Ответ 1
Если вы заранее знаете, что operator()
не будет перегружен, вы можете попытаться принять его адрес. Если operator()
возможно перегружен, то положительный результат будет означать, что присутствует operator()
, но отрицательный результат будет означать, что либо нет operator()
, либо, по крайней мере, две перегрузки.
Обратите внимание, что шаблон (как и ожидалось) приведет к нескольким перегрузкам operator()
. Однако, если вы знаете количество параметров шаблона, которые не дефолты, вы можете попробовать взять адрес operator()<T>
(для некоторого типа T
, который, надеюсь, не вызовет SFINAE).
Как последнее замечание, я бы посоветовал не пытаться тратить слишком много времени, пытаясь проверить функторы (или функции-члены по тем же причинам), не зная, какие аргументы проходят, как и то, что у вас уже есть. С++ 11 позволяет легко писать и использовать общий код, который функционирует на уровне выражения.
Ответ 2
Вы пытаетесь обнаружить шаблон функции члена operator()
с параметрами не выводимых параметров шаблона, которые на самом деле не являются "вызываемыми", а также являются беспредметными - вместо шаблона функции должно быть настоящее имя, потому что ваш пример действительно пропускает точку всей вещи operator
. Но пусть все равно разрешит вашу проблему.
Позвольте мне прочитать это с помощью подключаемого модуля для решения библиотеки, над которым я работаю, называемый CallableTraits (опять же, работа продолжается),
В то время как ваш случай не обрабатывается CallableTraits, библиотека использует технику, которую я собираюсь описать, чтобы решить очень похожую проблему. Этот метод является полным взломом, но он стандартно совместим и работает для меня на следующих платформах:
- GCC 5.2 и более поздние версии
- Clang 3.5 и более поздние версии
- Обновление Visual Studio 2015 1 - в основном работает
Примечание. Обновление Visual Studio 2015 Update 2 нарушено, поскольку оно неправильно выводит std::index_sequence<I...>
в частичную специализацию... Я подал отчет об ошибке. См. здесь для описания.
Примечание. Если ваша стандартная реализация библиотеки не имеет std::disjunction
, то вы можете вместо этого использовать примерную реализацию здесь.
Я вызываю метод червя шаблона. Это метапрограммирующий эквивалент плевки в глубокий, темный колодец, просто чтобы услышать, сколько времени потребуется, чтобы всплеск.
Что такое шаблонный червь?
- Шаблонный червь - это класс, который конвертируется во что угодно и все.
- Любые операторные выражения с шаблоном-червячным операндом всегда будут оцениваться другим червем шаблона.
- Шаблонный червь может использоваться только в неоценимом контексте. Другими словами, вы можете использовать его только тогда, когда
decltype
окружает выражение верхнего уровня, точно так же, как std::declval<T>()
.
Шаблонный червь вибрирует в местах, которые он не должен быть, и придерживается первого конкретного типа, который он может найти. Аналогичным образом настоящий герой будет придерживаться бетона в любой день июля.
Чтобы решить вашу проблему, мы начнем с каких-либо аргументов, а затем рекурсивно работаем до произвольного предела в 10. Мы пытаемся сделать вызов объекту функции (потенциала), пытаясь передать шаблонный червь посредством функции- вызов стиля, и аргумент типа шаблона (в соответствии с вашими требованиями).
Этот код не учитывает семантику INVOKE, потому что это занимает значительно больше кода. Если вам нужно это для работы с указателями-членами-функциями и данными-указателями-членами, вы можете использовать для этого свою собственную реализацию.
Возможно, я не охватил все операторы, и я мог бы не реализовывать их все правильно, но вы увидите точку.
Последнее:
Я знаю один улов. Тип возврата не может зависеть от зависимого имени (кроме операторов-членов).
Изменить: Кроме того, создание экземпляра/шаблона должно быть удобным для SFINAE (т.е. no 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 first example.
struct Test1 {
template<typename T>
T operator()() {
return{};
}
};
// Yup, it "callable", at least by OP definition...
static_assert(is_callable<Test1>::value, "");
// This matches OP second example.
struct Test2 {
template<typename T, typename U>
auto operator()() -> decltype(std::declval<T>() + std::declval<U>()) {
return{};
}
};
// Yup, it "callable", at least by OP definition...
static_assert(is_callable<Test2>::value, "");
// ints aren't callable, of course
static_assert(!is_callable<int>::value, "");
int main() {}