2016-05-31 15 views
5

Mam następujący API:Żywotność lambda schwytany odniesień w const lambdas

old_operation(stream, format, varArgs); 

I chcę napisać adaptera pozwalają napisać wezwanie następująco:

stream << operation(format, varArgs); 

Aby w tym celu używam tymczasowego obiektu, który przechowuje odwołania do varArgs i przeciąża operator<<, aby zastosować old_operation() w następujący sposób:

template<typename ...T> 
decltype(auto) storage(T&& ...t) { 
    return [&](auto&& f) ->decltype(auto) { 
     return std::forward<decltype(f)>(f)(t...); 
    }; 
} 

template<typename ...T> 
class Operation 
{ 
    using Storage = decltype(storage(std::declval<T>()...)); 
    public: 
     template<typename ...Args> 
     explicit Operation(Args&& ...args) : 
       mArgs(storage(std::forward<Args>(args)...)) {}; 
     template<class StreamType> 
     StreamType& Apply(StreamType& stream) 
     { 
      auto f = [&](auto&& ...xs) 
      { 
       old_operation(stream, std::forward<decltype(xs)>(xs)...); 
      } 
      mArgs(f); 
      return stream; 
     } 
    private: 
     Storage mArgs; 
}; 

template<typename ...Args> 
Operation<Args...> MakeOperation(Args&&... args) 
{ 
    return Operation<Args...>(std::forward<Args>(args)...); 
} 

template<class StreamType, typename ...Args> 
StreamType& operator<<(StreamType& stream, Operation<Args...>&& operation) 
{ 
    return operation.Apply(stream); 
} 

Działa to świetnie, ale teraz muszę dodać kilka using namespace deklaracji osadzone na wezwanie Operation:

powiedzmy mam

namespace X {namespace Y { namespace Z { int formater(double x) { return std::round(x); }}} 

A ja nie chcę, aby dodać wszystkie przestrzenie nazw dla tego połączenia , więc robię coś takiego:

#define OPERATION(...) \ 
    [&]() { \ 
     using namespace ::X:Y:Z; \ 
     return Operation("" __VA_ARGS__); }() \ 

która pozwala mi robić:

stream << OPERATION(format, formater(2.3)); 

Problem z lambda polega na tym, że tymczasniki są tworzone w innym zakresie niż wywołanie Apply(), które jest UB.

Nie wiem, czy dodając const constifier do mArgs, przedłuży to żywotność przechwyconych referencji, jak wspomniano: here. Nie jestem pewien, czy to ma zastosowanie, zakładam, że są to referencje oparte na stosach i że dodając kwalifikator const do mArgs, kwalifikator zostanie zastosowany do przechwyconych odniesień.

+1

Przedłużenie okresu ważności tymczasowego z "const &" dotyczy tylko lokalnych zmiennych funkcyjnych. Nie możesz przekazać tymczasowego do klasy, a następnie trzymać się go za pomocą 'const &'. – NathanOliver

+0

Obawiałem się tego, będę nadal próbował znaleźć inne rozwiązanie niż – dlavila

+0

Zastanawiam się, dlaczego coś tak prostego jak 'szablon Strumień & operator << (Strumień & strm, Format && f, Rest && ... vargs) { old_operation (stm, std :: forward (f), std :: forward (vargs) ...); powrót strm; } 'nie będzie działać? Po prostu pytam z ciekawości. – Arunmu

Odpowiedz

3
template<typename ...T> 
decltype(auto) storage(T&& ...t) { 
    return [&](auto&& f) ->decltype(auto) { 
    return std::forward<decltype(f)>(f)(t...); 
    }; 
} 

jest to funkoder w stylu "w stylu" (no cóż, wariantowy, który nie jest bardzo haskellowy). Zajmuje on Ts... i zwraca funkcję typu ((Ts...)->U)->U, tzn. Że wie, jak ocenić funkcję na argumentach, które do niej przekazałeś. To sprawia, że ​​storage typu (Ts...)->(((Ts...)->U)->U) dla trochę zabawy algebraicznej.

Podejrzewam, że masz problem z tym, że masz tymczasowe pliki, których nie przechowujesz. Zwykle nieprzechowywanie tymczasowych plików przekazywanych do funkcji, gdzie wartość zwracana zależy od czasu istnienia tych tymczasowych, skutkuje kruchym kodem.

Jeśli masz C++ 1z experimental::apply możemy to zrobić:

template<class... Ts> 
decltype(auto) storage(Ts&&... ts) { 
    return 
    [tup=std::tuple<Ts...>(std::forward<Ts>(ts)...)] 
    (auto&& f) 
    ->decltype(auto) mutable 
    { 
    return std::experimental::apply(decltype(f)(f), std::move(tup)); 
    }; 
} 

która zwraca jeden strzał opóźnione wywołanie std::apply. Apply przyjmuje funkcję i krotkę i przekazuje argumenty krotki do funkcji. Poprawnie obsługuje referencje i r/l-wartość. Tymczasem kontener krotki upraszcza przechwytywanie i pozwala nam z łatwością warunkowo przechowywać wartości r, zachowując wartości l jako referencje.

Myślę, że rozwiązuje to twój problem, ponieważ tymczasowe są przenoszone - do krotki zamiast przechwytywania przez odniesienie, podczas gdy non-tymczasowe są przechowywane przez odniesienie.

Powinny istnieć implementacje std::experimental::apply, które są lepsze niż wszystko, co mogę tutaj łatwo szkicować.

+0

miło, robiłem coś podobnego wcześniej, ale zamiast przechowywać lambda z krotką wewnątrz mojego obiektu 'operation' przechowywałam krotkę, wtedy rozpakowywałam krotkę bezpośrednio do' '' ' old_operation' wewnątrz wywołania 'Apply', ale nie byłem całkowicie przekonany o używaniu krotek, ponieważ długie czasy kompilacji – dlavila