Tworzenie szablonu na lambdzie w C++0x

Napisałem klasę cech, która pozwala mi wyodrębnić informacje o argumentach i typie funkcji lub obiektu funkcji w C++0x (testowane z gcc 4.5.0). Ogólny przypadek obsługuje obiekty funkcyjne:

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>>;
};

Wtedy mam specjalizację dla funkcji prostych w zasięgu globalnym:

template <typename R, typename... A>
struct function_traits<R (*)(A...)> {
    // ...
};

To działa dobrze, mogę przekazać funkcję do szablonu lub obiektu funkcji i działa poprawnie:

template <typename F>
void foo(F f) {
    typename function_traits<F>::whatever ...;
}

int f(int x) { ... }
foo(f);

Co jeśli zamiast przekazać funkcję lub obiekt funkcji do foo, chcę przekazać wyrażenie lambda?

foo([](int x) { ... });

Problem polega na tym, że żadna specjalizacja function_traits<> nie ma zastosowania. Szkic C++0x mówi, że typ wyrażenia jest "unikalnym, nienazwanym, nieunijnym typem klasy". Demanglowanie wyniku wywołania typeid(...).name() na wyrażeniu daje mi to, co wydaje się być wewnętrzną konwencją nazewnictwa GCC dla lambda, main::{lambda(int)#1}, a nie coś, co składniowo reprezentuje nazwę typową C++.

Krótko mówiąc, czy jest coś, co mogę umieścić w szablonie tutaj:

template <typename R, typename... A>
struct function_traits<????> { ... }

To pozwoli tej klasie cech zaakceptować wyrażenie lambda?

Author: Georg Fritzsche, 2010-04-01

3 answers

Myślę, że możliwe jest wyspecjalizowanie cech dla Lambda i dopasowanie wzorców na podpisie nienazwanego functora. Oto kod, który działa na g++ 4.5. Chociaż działa, dopasowanie wzorca na lambdzie wydaje się działać wbrew intuicji. Mam komentarze w linii.

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; });
}
 17
Author: Sumant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-02 04:46:40

Sztuczka void_t może pomóc. Jak działa 'void_t' ?

Jeśli nie masz C++17, musisz podać definicję void_t:

template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

Dodaj dodatkowy argument szablonu do oryginalnego szablonu, domyślnie void:

template <typename T, typename = void>
struct function_traits;

Obiekt cech dla prostych funkcji jest taki sam jak już masz:

template <typename R, typename... A>
struct function_traits<R (*)(A...)>
{
    using return_type = R;
    using class_type  = void;
    using args_type   = std:: tuple< A... >;
};

Dla metod non-const:

template <typename R, typename... A>
struct function_traits<R (*)(A...)>
{
    using return_type = R;
    using class_type  = void;
    using args_type   = std:: tuple< A... >;
};

Nie zapomnij const metody:

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... >;
};

Wreszcie ważna cecha. Dana klasa typ, w tym typy lambda, chcemy przejść z T do decltype(&T::operator()). Chcemy mieć pewność, że ta cecha jest dostępna tylko dla typów T, dla których ::operator() jest dostępna, a to robi dla nas void_t. Aby wyegzekwować to ograniczenie, musimy umieścić &T::operator() gdzieś w podpisie cechy, stąd 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())   >
{
};

Metoda operator() w lambdzie (non-mutable, non-generic) to const, co wyjaśnia, dlaczego potrzebujemy szablonu const powyżej.

Ale ostatecznie jest to bardzo restrykcyjne. Nie będzie to działać z ogólnymi lambdami lub obiektami z szablonami operator(). Jeśli ponownie rozważysz swój projekt, znajdziesz inne podejście, które jest bardziej elastyczne.

 2
Author: Aaron McDaid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:12

Delegując część pracy do serii szablonów funkcji zamiast szablonu klasy , możesz wyodrębnić odpowiednie informacje.

Najpierw jednak powinienem powiedzieć, że odpowiednia metoda jest metodą const, dla Lambda (Dla Nie-przechwytywania, nie-generycznego, nie-mutable lambda). Więc nie będziesz w stanie odróżnić prawdziwej lambdy od tej: {]}

struct {
    int operator() (int) const { return 7; }
} object_of_unnamed_name_and_with_suitable_method;

Dlatego muszę założyć, że nie chcesz "specjalnego traktowania" lambdy i nie chcesz sprawdzić, czy typ jest typem lambda, a zamiast tego chcesz po prostu wyodrębnić typ zwracany i typ wszystkich argumentów dla dowolnego obiektu, który jest wystarczająco prosty. Przez "dość proste" Rozumiem na przykład, że metoda operator() jest , a nie sama w sobie szablonem. I, dla informacji bonusowych, logiczny, aby powiedzieć nam, czy operator() metoda była obecna i używana, w przeciwieństwie do zwykłej starej funkcji.



// 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>() ) )
{
};
 1
Author: Aaron McDaid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-22 18:51:13