Вы пытаетесь обнаружить шаблон функции-члена operator()
с невосстановленными параметрами шаблона, который на самом деле не «вызывается» вообще, а также является своего рода бессмысленным - шаблон функции вместо этого должен иметь настоящее имя, потому что ваш пример действительно упускает смысл всего этого. Но давайте все равно решим вашу проблему.
Позвольте мне предварять это заглушкой для библиотечного решения, над которым я работаю, под названием CallableTraits (опять же, в процессе разработки).
Хотя CallableTraits не обрабатывает ваше дело, библиотека использует метод, который я собираюсь описать, для решения очень похожей проблемы. Техника - полный взлом, но она соответствует стандарту и работает для меня на следующих платформах:
- GCC 5.2 и более поздние
- Clang 3.5 и более поздние
- Visual Studio 2015, обновление 1 - в основном работает
Примечание. Обновление 2 для Visual Studio 2015 не работает, поскольку оно неправильно выводит std::index_sequence<I...>
при частичной специализации ... Я отправил отчет об ошибке. См. здесь для описания.
Примечание. Если ваша стандартная реализация библиотеки еще не имеет std::disjunction
, тогда вы можете использовать образец реализации здесь .
Я называю технику шаблонным червем . Это метапрограммирующий эквивалент плевания в глубокий, темный колодец, просто чтобы узнать, сколько времени занимает всплеск.
Что такое шаблонный червь?
- Шаблонный червь - это класс, который можно преобразовать во что угодно и что угодно.
- Любые операторные выражения с операндом шаблонного червя всегда будут оцениваться как другой шаблонный червь.
- Шаблонный червь может использоваться только в неоцененном контексте. Другими словами, вы можете использовать его только тогда, когда
decltype
окружает выражение верхнего уровня, как std::declval<T>()
.
Шаблонный червь извивается в тех местах, в которых он не должен быть, и придерживается первого конкретного типа, который он может найти. Аналогичным образом настоящий червь прилипнет к бетону в любой день июля.
Чтобы решить вашу проблему, мы начнем без аргументов, затем рекурсивно работаем до произвольного предела 10. Мы пытаемся вызвать (потенциальный) функциональный объект, пытаясь передать червя шаблона функцией- вызов стиля и аргумент типа шаблона (в соответствии с вашими требованиями).
Этот код не учитывает семантику INVOKE , потому что для этого требуется значительно больше кода. Если вам это нужно для работы с указателями на функции-члены и указателями на данные-члены, вы можете применить для этого собственную реализацию.
Возможно, я не охватил все операторы, и, возможно, я не реализовал их все правильно, но вы поймете смысл.
И последнее:
Я знаю один улов. Тип возвращаемого значения не может зависеть от зависимого имени (кроме операторов-членов).
Редактировать: Кроме того, вызов / создание шаблона должны быть дружественными к SFINAE (т. Е. Нет static_assert
с).
Без лишних слов, вот ваше автономное решение (хотя вы, возможно, хотели бы, чтобы вы не спрашивали):
#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() {}