Специализирую шаблон на лямбде в C++0x
Я написал класс черт, который позволяет мне извлекать информацию об аргументах и типе функции или функционального объекта в C++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<>
применяется. В проекте C++ 0x говорится, что тип выражения является "уникальным, безымянным, не объединенным типом класса". Разоблачение результата вызова typeid(...).name()
в выражении дает мне то, что кажется внутренним соглашением именования Gcc для лямбда, main::{lambda(int)#1}
, а не то, что синтаксически представляет типовое имя C++.
Короче говоря, есть ли что-нибудь, что я могу вставить в шаблон здесь:
template <typename R, typename... A>
struct function_traits<????> { ... }
что позволит этому классу признаков принять лямбда-выражение?
3 ответа
Я думаю, что можно специализировать черты для лямбд и сопоставлять образцы по сигнатуре безымянного функтора. Вот код, который работает на 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; });
}
void_t
трюк может помочь. Как работает `void_t`?
Если у вас нет C++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() в (неmutable
не родовые) лямбды const
что объясняет, почему нам нужно const
Шаблон выше.
Но в конечном итоге это очень ограничительно. Это не будет работать с общими лямбдами или объектами с шаблонами operator()
, Если вы пересмотрите свой дизайн, вы найдете другой, более гибкий подход.
Передав часть работы серии шаблонов функций вместо шаблона класса, вы можете извлечь соответствующую информацию.
Во-первых, я должен сказать, что соответствующий метод 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>() ) )
{
};