2014-04-25 8 views
12

W poniższym kodzie kompilator rzuca w milczeniu wskaźnik funkcji "powróć po kopii" do funkcji std :: return-by-const-reference. Kiedy wywoływana jest instancja std :: function, zwracane jest odwołanie do kopii, a aplikacja ulega awarii (przez większość czasu;).Czy funkcja std :: pozwala na niejawny rzut od odwołania do kopii w swoim typie zwracanym?

Dla porównania, zwykłe wskaźniki funkcji nie pozwalają na tę niejawną obsadę, więc zastanawiam się, czy powinienem złożyć skargę na dostawcę kompilatora (w tym przypadku gcc 4.8), czy też takie zachowanie jest nakazane przez standard ?

#include <iostream> 
#include <functional> 

typedef std::function<const std::string&(const std::string& x)> F; 

std::string bad(const std::string& x) { return x; } 
const std::string& good(const std::string& x) { return x; } 

typedef const std::string& (*FP)(const std::string&); 

int main(int, char**) { 
    std::cout << F(&good)("hello") << std::endl; 
    std::cout << F(&bad)("hello") << std::endl; 

    FP a = &good; 
    // FP b = &bad; Not allowed! 

    return 0; 
} 

PS: To jest uproszczoną wersją prawdziwych problemów świata, gdzie bad faktycznie lambda powrocie członkiem jakiegoś typu:

typedef std::function<const std::string&(const X& x)> F; 
F f = [](const X& x) { return x->member(); }; 

Zajęło nam trochę czasu, aby dowiedzieć się, że typ zwracany tej lambda została wyprowadzona do std::string , a nie const std::string&, co spowodowało awarię.

+0

Czy 'x-> member()' zwraca 'std :: string' lub' const std :: string & '? – Stas

+1

'const std :: string &', ale w celu odliczenia zwrotu, nie ma znaczenia, czy jest to odnośnik, czy kopia - zostanie to wydedukowane jako kopia (rodzaj zepsuty) (patrz http: //akrzemi1.wordpress .com/2012/03/27/gotyk-typu-wnioskowania /) –

+0

Czy nazwałeś typy błędnie w tym ostatnim akapicie? A może źle zrozumiałem pytanie? –

Odpowiedz

6

Wygląda to na narożnik. Definicja konstruktor w §2.8.11.2.1/7 mówi:

Wymaga:F będzie CopyConstructible. f będzie Callable (20.8.11.2) dla typów argumentów ArgTypes i powrócić typ R. [...]

§2.8.11.2/2 mówi:

wywoływalnym obiekt f typu F można wywoływać dla typów argumentów ArgTypes i zwracać typ R, jeśli wyrażenie "nieuzgodniony operand" (klauzula 5) uznano za niezaawansowany operand (klauzula 5), ​​a następnie utworzono (20.8.2): .

i ostatni §20.8.2/2 mówi:

zdefiniować powołaniem się (K, t1, t2, ..., tN, R), jak powołaniem się (K, t1, t2, .. ., tn) przekształca się w sposób dorozumiany R.

Oczywiście T pośrednio przekształca się T const & a więc w przypadku braku dalszych ograniczeń, konstruktor powinny być dozwolone.

Jednak wywoływanie takiej funkcji wymaga wzięcia powracającego odniesienia do tymczasowego, którego żywotność kończy się, zanim odniesienie zostanie zwrócone, co jest niezdefiniowanym zachowaniem. A gdy coś jest Nieokreślonym Zachowaniem, implementacja może zrobić, co mu się podoba. Niestety niezdefiniowane zachowanie występuje tylko podczas wywoływania, więc nadal nie jest zgodne z wykrywaniem go w czasie budowy.

Ponieważ wywoływanie go jest jedynym zastosowaniem obiektu, byłoby lepiej, gdyby było zabronione. Tak więc ten należy uznać za wadę w specyfikacji.

W każdym razie, zaleciłbym wprowadzenie go na odpowiedniej liście mailingowej gcc.Opiekunowie są gotowi odejść od specyfikacji nieco w tym przypadku lub przynajmniej mogliby podnieść lub pomóc ci podnieść kwestię z komitetem C++, ponieważ pracują z nim regularnie.

+1

Implememacje nie mogą tego zablokować, ponieważ nie ma niezdefiniowanych zachowań do czasu wywołania ... Nieuznana ekspresja jest dobrze uformowany. Jest to usterka standardu. – Yakk

+0

@Yakk: Masz rację, wydaje się, że problem dotyczy specyfikacji. –

Powiązane problemy