2016-05-22 8 views
7

Próbowałem ciężko, aby funkcja lambda zwróciła wartość przez odniesienie bez tworzenia kopii odnośnej wartości. Mój przykładowy kod ilustruje problem. Kompiluje się i działa poprawnie, ale z "//" skomentowaną linią zamiast powyższą linią, tak nie jest. znalazłem dwa obejścia (oba przedstawione na moim przykładzie):Powrotna referencja lambda

  • owinąć wynik z std :: Ref()
  • zwracają wskaźnik zamiast odniesienie

Ale oba są obejścia nie to, czego naprawdę chcę i nie rozumiem, dlaczego są one konieczne: wyrażenie "makeRefA()" ma już typ, który zwraca funkcja lambda (const A &) i dlatego nie należy go kopiować ani konwertować. Nawiasem mówiąc: Konstruktor kopiowania jest naprawdę wywoływany, gdy go nie usuwam jawnie (co w moim "rzeczywistym" kodzie jest problemem wydajności). Dla mnie wygląda jak błąd kompilatora, ale próbowałem z kilkoma kompilatorami C++ 11, które wszystkie wykazują ten sam błąd. Czy jest coś wyjątkowego w odniesieniu do instrukcji "return" w funkcji lambda?

#include <functional> 
#include <iostream> 

struct A { 
    A(int i) : m(i) { } 
    A(const A&) = delete; 
    int m; 
}; 

void foo(const A & a) { 
    std::cout << a.m <<'\n'; 
} 

const A & makeRefA() { 
    static A a(3); 
    return a; 
} 

int main() { 
    std::function<const A&()> fctRef = [&] 
    { return std::ref(makeRefA()); }; //compiles ok 
    //{ return makeRefA(); }; //error: use of deleted function 'A::A(const A&)' 
    foo(fctRef()); 

    std::function<const A*()> fctPtr = 
    [&] { return &makeRefA(); }; 
    foo(*fctPtr()); 

    return 0; 
} 

wyjściowa:

3 
3 

Odpowiedz

7

domyślnie automatycznie wywnioskować typ lambda jest wersja bez odniesienia typu

... typem zwrotu jest typ zwróconego expressi on (po lwartej do rwartości, niejawnej konwersji między tablicami i wskaźnikami lub funkcji do wskaźnika); (source)

Jeśli chcesz zwrócić typ z odniesieniem, będziesz musiał określić go bardziej wyraźnie. Oto kilka opcji:

[&]() 
-> decltype(makeRefA()) 
{ return makeRefA()); }; 

lub po prostu być w pełni jawne o rodzaju powrotnej z ->

[&]() 
-> const A& 
{ return makeRefA(); } 

Jeśli używasz C++ 14, następnie wystarczy użyć decltype(auto),

[&]() 
-> decltype(auto) 
{ return makeRefA(); } 

zasady dotyczące decltype mogą być czasami skomplikowane. Ale fakt, że makeRefA() jest wyrażeniem (w przeciwieństwie do zwykłego nazwania zmiennej) oznacza, że ​​typ wyrażenia (const A&) jest wiernie zwracany przez decltype(makeRefA()).

+0

"Zwróci referencję, gdy typ wyrażenia jest odniesieniem" <- Prawidłowe-ish. Wyrażenie * typ * nigdy nie będzie odniesieniem, ale otrzymasz właściwą odpowiedź, jeśli myślisz o tym w ten sposób. – Barry

+0

odpowiedź, dziękuję! – Stefan

+1

Działa nawet bez dodatkowych nawiasów, tj. std :: function fctRef = [&]() -> decltype (makeRefA()) {return makeRefA();}; – Stefan

7

można określić typ zwracanej

#include <functional> 
#include <iostream> 

struct A { 
    A(int i) : m(i) { } 
    A(const A&) = delete; 
    int m; 
}; 

void foo(const A & a) { 
    std::cout << a.m <<'\n'; 
} 

const A & makeRefA() { 
    static A a(3); 
    return a; 
} 

int main() { 
    std::function<const A&()> fctRef = [&]()->const A& 
// { return std::ref(makeRefA()); }; //compiles ok 
    { return makeRefA(); }; // works 
    foo(fctRef()); 

    std::function<const A*()> fctPtr = 
    [&] { return &makeRefA(); }; 
    foo(*fctPtr()); 

    return 0; 
} 
+0

Wow jakoś całkowicie przeszedł obok statycznej części:/ –

+0

OK dzięki, to jest inne obejście, ale dlaczego jest to konieczne dla funkcji zwracającej "const A &", podczas gdy dla funkcji zwracającej "const A *" to nie jest? – Stefan

+0

@ stefan: to nie jest "obejście". W pewnych okolicznościach możesz pominąć typ zwrotu funkcji lambda. To nie jedna z tych okoliczności. Z tego samego powodu 'auto a = b' nie czyni' a' odniesieniem do 'b'. – rici

2

Według http://en.cppreference.com/w/cpp/language/lambda, przepisy te stosuje się do lambdas bez typ zwracany spływu:

  • w C++ 11, lwartość do RValue, niejawna tablica do wskaźnika lub funkcji do wskaźnika konwersja zostanie zastosowana do typu zwracanego wyrażenia. (Tutaj konwersja l-wartości do rv jest tym, co cię uderza.)
  • W C++ 14 i późniejszych, typ jest wyprowadzany jak dla funkcji, której typ powrotu jest zadeklarowany jako auto; a to z kolei jest zgodne z regułami odliczania argumentów za szablon. Następnie, ponieważ auto nie zawiera specyfikacji odniesienia, oznacza to, że odniesienia i kwalifikatory cv zostaną zignorowane.

Efekt jest prawdopodobnie pożądane w większości sytuacji, na przykład: w tym wyrażeniu lambda

[](const std::vector<int>& v) { return v[0]; } 

prawdopodobnie zamierza zwrócić int, choć std::vector<int>::operator[] const powraca const int&.

Jak wspomnieli inni, można zastąpić to zachowanie, dając wyraźny końcowy typ powrotu.

Powiązane problemy