Специализация шаблона на лямбда в С++ 0x
Я написал класс признаков, который позволяет мне извлекать информацию о аргументах и типе функции или объекта функции в С++ 0x (проверяется с помощью gcc 4.5.0). Общий случай обрабатывает объекты функции:
template <typename F>
struct function_traits {
template <typename R, typename... A>
struct _internal { };
template <typename R, typename... A>
struct _internal<R (F::*)(A...)> {
// ...
};
typedef typename _internal<decltype(&F::operator())>::<<nested types go here>>;
};
Затем у меня есть специализация для простых функций в глобальной области:
template <typename R, typename... A>
struct function_traits<R (*)(A...)> {
// ...
};
Это отлично работает, я могу передать функцию в шаблон или объект функции, и он работает правильно:
template <typename F>
void foo(F f) {
typename function_traits<F>::whatever ...;
}
int f(int x) { ... }
foo(f);
Что делать, если вместо передачи функции или объекта функции в foo
, я хочу передать лямбда-выражение?
foo([](int x) { ... });
Проблема заключается в том, что не применяется ни специализация function_traits<>
. В проекте С++ 0x говорится, что тип выражения является "уникальным, неназванным, неединичным типом класса". Погрешность результата вызова typeid(...).name()
в выражении дает мне то, что кажется gcc внутренним соглашением именования для лямбда, main::{lambda(int)#1}
, а не то, что синтаксически представляет собой имя файла С++.
Короче, есть ли что-нибудь, что я могу внести в шаблон здесь:
template <typename R, typename... A>
struct function_traits<????> { ... }
который позволит этому классу признаков принимать лямбда-выражение?
Ответы
Ответ 1
Я думаю, что можно специализировать черты для лямбда и сопоставлять шаблоны подписи неназванного функтора. Вот код, который работает на g++ 4.5. Хотя это работает, сопоставление образцов на лямбда, похоже, работает вопреки интуиции. У меня есть встроенные комментарии.
struct X
{
float operator () (float i) { return i*2; }
// If the following is enabled, program fails to compile
// mostly because of ambiguity reasons.
//double operator () (float i, double d) { return d*f; }
};
template <typename T>
struct function_traits // matches when T=X or T=lambda
// As expected, lambda creates a "unique, unnamed, non-union class type"
// so it matches here
{
// Here is what you are looking for. The type of the member operator()
// of the lambda is taken and mapped again on function_traits.
typedef typename function_traits<decltype(&T::operator())>::return_type return_type;
};
// matches for X::operator() but not of lambda::operator()
template <typename R, typename C, typename... A>
struct function_traits<R (C::*)(A...)>
{
typedef R return_type;
};
// I initially thought the above defined member function specialization of
// the trait will match lambdas::operator() because a lambda is a functor.
// It does not, however. Instead, it matches the one below.
// I wonder why? implementation defined?
template <typename R, typename... A>
struct function_traits<R (*)(A...)> // matches for lambda::operator()
{
typedef R return_type;
};
template <typename F>
typename function_traits<F>::return_type
foo(F f)
{
return f(10);
}
template <typename F>
typename function_traits<F>::return_type
bar(F f)
{
return f(5.0f, 100, 0.34);
}
int f(int x) { return x + x; }
int main(void)
{
foo(f);
foo(X());
bar([](float f, int l, double d){ return f+l+d; });
}
Ответ 2
Тройка void_t
может помочь. Как работает` void_t`?
Если у вас нет С++ 17, вам нужно включить определение void_t
:
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
Добавьте дополнительный шаблонный шаблон к исходному шаблону, по умолчанию - void
:
template <typename T, typename = void>
struct function_traits;
Объект объектов для простых функций такой же, как и у вас:
template <typename R, typename... A>
struct function_traits<R (*)(A...)>
{
using return_type = R;
using class_type = void;
using args_type = std:: tuple< A... >;
};
Для неконстантных методов:
template <typename R, typename... A>
struct function_traits<R (*)(A...)>
{
using return_type = R;
using class_type = void;
using args_type = std:: tuple< A... >;
};
Не забудьте const
методы:
template <typename R, typename C, typename... A>
struct function_traits<R (C::*)(A...) const> // const
{
using return_type = R;
using class_type = C;
using args_type = std:: tuple< A... >;
};
Наконец, важная черта. Учитывая тип класса, включая типы лямбда, мы хотим перейти от T
в decltype(&T::operator())
. Мы хотим убедиться, что этот признак доступен только для типов T
, для которых ::operator()
доступен, и это то, что void_t
для нас. Чтобы принудительно применить это ограничение, нам нужно помещать &T::operator()
в подпись признаков где-то, следовательно template <typename T> struct function_traits<T, void_t< decltype(&T::operator())
template <typename T>
struct function_traits<T, void_t< decltype(&T::operator()) > >
: public function_traits< decltype(&T::operator()) >
{
};
Метод operator() в (non mutable
, non-generic) lambdas равен const
, что объясняет, почему нам нужен шаблон const
выше.
Но в конечном итоге это очень ограничительно. Это не будет работать с общими лямбдами или объектами с шаблоном operator()
. Если вы передумаете свой дизайн, вы найдете более гибкий подход, более гибкий.
Ответ 3
Делегируя часть работы на ряд шаблонов функций вместо шаблона класса, вы можете извлечь соответствующую информацию.
Во-первых, я должен сказать, что соответствующий метод - это метод const
, для лямбда (для не-захватывающего, не общего, не mutable
лямбда). Таким образом, вы не сможете отличить истинную лямбду от этого:
struct {
int operator() (int) const { return 7; }
} object_of_unnamed_name_and_with_suitable_method;
Поэтому я должен предположить, что вы не хотите "специального лечения" для лямбда, и вы не хотите проверять, является ли тип лямбда-типом, и вместо этого вы хотите просто извлечь возвращаемый тип и тип всех аргументов, для любого достаточно простого объекта. Под "достаточно простым" я имею в виду, например, что метод operator()
сам по себе не является шаблоном. И, для информации о бонусе, логическое значение, чтобы сказать нам, если метод operator()
присутствовал и использовался, в отличие от простой старой функции.
// First, a convenient struct in which to store all the results:
template<bool is_method_, bool is_const_method_, typename C, typename R, typename ...Args>
struct function_traits_results {
constexpr static bool is_method = is_method_;
constexpr static bool is_const_method = is_const_method_;
typedef C class_type; // void for plain functions. Otherwise,
// the functor/lambda type
typedef R return_type;
typedef tuple<Args...> args_type_as_tuple;
};
// This will extract all the details from a method-signature:
template<typename>
struct intermediate_step;
template<typename R, typename C, typename ...Args>
struct intermediate_step<R (C::*) (Args...)> // non-const methods
: public function_traits_results<true, false, C, R, Args...>
{
};
template<typename R, typename C, typename ...Args>
struct intermediate_step<R (C::*) (Args...) const> // const methods
: public function_traits_results<true, true, C, R, Args...>
{
};
// These next two overloads do the initial task of separating
// plain function pointers for functors with ::operator()
template<typename R, typename ...Args>
function_traits_results<false, false, void, R, Args...>
function_traits_helper(R (*) (Args...) );
template<typename F, typename ..., typename MemberType = decltype(&F::operator()) >
intermediate_step<MemberType>
function_traits_helper(F);
// Finally, the actual `function_traits` struct, that delegates
// everything to the helper
template <typename T>
struct function_traits : public decltype(function_traits_helper( declval<T>() ) )
{
};