2015-03-05 15 views
6

mam takiej klasy użytkowy:inicjalizacji i lambda typu argumentu

struct Atreturn 
{ 
    std::function<void()> funcdestr; 
    Atreturn(std::function<void()> fd): funcdestr(fd) {} 
    ~Atreturn() { funcdestr(); } 
}; 

Uwaga nie explicit atrybutu z konstruktora.

Możliwe stosowanie powinno być:

  1. Direct-inicjowania wywołania konstruktora:

    Atreturn hook ([something]() { DestroySomething(something); }); 
    
  2. kopiowaniem inicjowania wywołania konstruktora:

    Atreturn hook = [something]() { DestroySomething(something); }; 
    
  3. Direct-lista-inicjowania wywołania konstruktora:

    Atreturn hook { [something]() { DestroySomething(something); }}; 
    

Teraz pytanie: do mojej najlepszej wiedzy, metoda # 1 i # 2 powinny być dozwolone, ponieważ są one teoretycznie to samo, pod warunkiem, że nie ma explicit w konstruktorze, natomiast # 3 nie powinno być dozwolone, ponieważ ta składnia uniemożliwia konwersje (przynajmniej tak jest w przypadku int, jeśli próbowałeś int{2.1}).

Jednak gcc 4.9 pozwala na metody nr 1 i nr 3, ale nie na nr 2 (i mówi conversion from '...::<lambda()>' to non-scalar 'Atreturn' type requested). Brzmi to szalenie, ponieważ zwykle dzieje się tak tylko wtedy, gdy masz konstruktora explicit. Czy ktoś może wyjaśnić, dlaczego?

Dodatkowo, pozwolę sobie bardziej wyjaśnić ten problem: potrzebuję niezbyt skomplikowanej składni, aby zainicjować ten obiekt Atreturn, przynajmniej bez potrzeby stosowania dodatkowych nawiasów klamrowych lub nawiasów. Problem polega na tym, że edytory z funkcją auto-wcięcia mają problemy z prawidłowym powtórzeniem, gdy argumentem jest C++ 11 lambda. Więc muszę trochę składni, która może być wyrażona jako:

Atreturn BLAH BLAH BLAH [something]() { DestroySomething(something); }; 
+1

(Dodaj również konstrukcję inicjującą listę kopiowania: 'Areturn hook = {...}'?) –

+0

To jest to samo co metoda # 3, to = jest opcjonalne w C++ 11. – Ethouris

+0

# 1 i # 2 to * nie * to samo. Byłoby, gdyby typ z prawej był "Atreturn", ale tak nie jest. – Angew

Odpowiedz

7

podczas # 3 nie powinny być dozwolone, ponieważ ta składnia zapobiega konwersji (przynajmniej jest tak do int jeśli próbował int {2,1}).

To nie w porządku. Zasadą jest, że zawężanie konwersji jest niedozwolone. Dopuszczalne są inne typy konwersji. int{2.1} jest konwersją zawężającą, ponieważ modyfikuje wartość, tracąc precyzję. int{2.0} nie jest konwersją zawężającą, ponieważ wartość nie jest zmieniana.

Powodem niepowodzenia nr 2 jest to, że wymaga dwóch niejawnych konwersji zdefiniowanych przez użytkownika, co jest zabronione.

koncepcyjnej, kopia inicjalizacja takich jak:

Atreturn hook = []() {}; 

odpowiada:

Atreturn hook = Atreturn([]() {}); 

(poza tym, że nie można nazwać „jawnych” konstruktorów, a kompilator może abstrahować Kopiuj).

Oznacza to, że najpierw lambda musiałaby niejawnie przekonwertować na function<void()>, a następnie musiałaby niejawnie przekonwertować na Atreturn. Obie te konwersje są "zdefiniowaną przez użytkownika sekwencją konwersji", co oznacza, że ​​wywołują konstruktora, a nie wbudowane konwersje, takie jak int do long, a standard mówi, że niejawna sekwencja konwersji nie może obejmować więcej niż jednej konwersji zdefiniowanej przez użytkownika.

Problem jest rzeczywiście niezwiązane lambdas można wykazać dokładnie ten sam błąd jak poniżej:

struct L { }; 
struct F { F(L) { } }; 
struct A { A(F) { } }; 
A a = L(); 

l.cc:4:9: error: conversion from ‘L’ to non-scalar type ‘A’ requested 
A a = L(); 
     ^

Ponownie, problem jest taki, że domniemany sekwencja konwersja L -> F -> A obejmuje dwie konwersje zdefiniowane przez użytkownika, który jest zabronione .

Nie mam zbyt wielkiego współczucia dla twojego problemu z koniecznością adaptacji kodu, aby pomóc w automatycznym wcięciu - kod nie powinien być zawikłany, aby pasował do wadliwego edytora. Jednak inną opcją byłoby dodanie konstruktora szablonu, który akceptuje wszystko, co można przekonwertować na std::function<void()>, np.

struct Atreturn 
{ 
    using func_type = std::function<void()>; 
    template<typename T, 
      typename Requires = decltype(func_type(std::declval<T&&>())> 
    Atreturn(T t) : funcdestr(std::move(t)) { } 
    ... 
}; 

pozwoli to lambda można przekształcić bezpośrednio do Atreturn bez konieczności niejawnego przemianę function<void()> pierwszy.

+0

Lambda do 'funkcji ' jest całkiem ok, ale gdzie widzisz potrzebę przekonwertowania tego na 'Atreturn'? Czy inicjalizacja za pomocą znaku równości nie powinna korzystać z konstruktora 1-argumentowego z parametrem tego typu? – Ethouris

+3

@Ethouris Nie, bezpośrednia inicjalizacja (brak '=') i inicjalizacja kopiowania (z '=') są * nie * takie same. Ten drugi wymaga stworzenia tymczasowego i użycia go jako argumentu dla ctor kopii (nawet jeśli jest to wyeliminowane w praktyce). – Angew

+0

@Ethouris, zaktualizowałem odpowiedź, aby to wyjaśnić. Po prawej stronie '=' reguły językowe mówią, że istnieje tymczasowy obiekt 'Atreturn'. Nawet jeśli tymczasowy błąd zostanie usunięty, reguły dotyczące przeciążania i konwersji muszą zachowywać się tak, jakby utworzono tymczasowy. –