2014-12-19 6 views
8

Mój szablon-fu jest raczej słaby. Mam ten kod:W jaki sposób można uzyskać kompilator C++ do pośredniego wydedukowania T?

template<typename T> 
void Foo(void(*func)(T*)) { } 

void Callback(int* data) { } 

int Test() 
{ 
    Foo(Callback); 
} 

... ale chciałbym coś bardziej czytelny niż bolesnego wskaźnik funkcji składni C jest z void(*func)(T*).

Ktoś w moim zespole zaproponował to:

template<typename T> 
struct Types 
{ 
    typedef void Func(T*); 
}; 

template<typename T> 
void Foo2(typename Types<T>::Func* func) {} 

void Test2() 
{ 
    Foo2(Callback);  // could not deduce template argument for 'T' 
    Foo2<int>(Callback); // ok 
} 

(ja wciąż zastanawiając się, czy to jest rzeczywiście bardziej czytelny, ale to osobna kwestia).

Jak mogę pomóc postać kompilatora się co to jest T bez konieczności wyraźnego określania go w dzwoniącym?

+7

Nie sądzę, że sugerowane rozwiązanie jest bardziej czytelne. Nawet odwrotnie. Ale tylko moja opinia. –

+2

Wszystko po lewej stronie '::' jest kontekstem niewydechem. W C++ 11 użyj szablonów aliasów: 'template using Func = void (A *); template void Foo (Func * func) {} ' –

+1

W twoim kodzie wywołanie zwrotne ma parametr" T * ". W jaki sposób 'Foo' wie, co przekazać do wywołania zwrotnego? To nie wygląda na prawdziwy kod –

Odpowiedz

9

Można wyodrębnić T z typu funkcji za pomocą klasy cech.

template<class F> 
struct CallbackTraits; 

template<class T> 
struct CallbackTraits<void(*)(T)> 
{ 
    typedef T ArgumentType; 
}; 

Twój przykład może być modyfikowana tak:

template<typename F> 
void Foo(F func) 
{ 
    typedef typename CallbackTraits<F>::ArgumentType T; 
} 

void Callback(int* data) { } 

int Test() 
{ 
    Foo(Callback); 
} 

Technika ta jest stosowana w doładowania TYPU cechami Biblioteka: http://www.boost.org/doc/libs/1_57_0/libs/type_traits/doc/html/boost_typetraits/reference/function_traits.html

Ten blogu przechodzi w nieco bardziej szczegółowo o implementacja techniki: https://functionalcpp.wordpress.com/2013/08/05/function-traits/

Niestety to pproach ukrywa informacje w sygnaturze Foo o ograniczeniach dotyczących przekazywanego argumentu. W powyższym przykładzie argument musi być funkcją typu void(T*).

Ta alternatywa składnia działa tak samo jak oryginalny przykład będąc nieco bardziej czytelne:

template<typename T> 
void Foo(void func(T*)) { } 

Inną alternatywą składni, które mogą być bardziej czytelne można osiągnąć za pomocą C++ 11 za szablonów alias następująco:

template<typename T> 
using Identity = T; 

template<typename T> 
void Foo(Identity<void(T*)> func) { } 

Niestety, najnowszy MSVC nie może tego skompilować, zgłaszając wewnętrzny błąd kompilatora.

+0

To jest całkiem niezłe i jedyne co mi się nie podoba to utrata dokumentacji dotyczącej "func" dla Intellisense. Czytelnik musi przejść na kilka poziomów pośrednich IDE, aby dowiedzieć się, jakie są ograniczenia "F". Oprócz dodania komentarza (który pojawi się w wielu implementacjach Intellisense), czy masz jakieś sugestie, jak zachować część dokumentacji typu? – scobi

+0

@scobi Możesz użyć 'static_assert', aby dać czytelny błąd kompilacji, chyba że' F' jest typem obiektu, który można wywołać z żądaną liczbą argumentów i/lub typu zwracanego. Jakie są ograniczenia dla 'F' w twoim przykładzie? Prawdopodobnie wybrałbym również lepszą nazwę dla 'F' w zależności od ograniczeń - np. 'CallableObjectType'. Możesz również użyć polecenia 'std :: enable_if', aby zapobiec wywołaniu' Foo' z argumentem, który nie spełnia ograniczeń, ale wtedy nie powiedzie się po cichu. – willj

+0

Ograniczenia czasu kompilacji są w porządku, ale to, co robię, to dokumenty. Gdy ktoś nazywa "Foo", musi wiedzieć, co przekazać. Zwykle robi się to za pomocą IntelliSense opisującego typ arg. Nie chciałbym, żeby ktoś musiał skompilować (lub przepuścić przez źródło), aby dowiedzieć się, czego rzeczywiście chce funkcja. Nazwa taka jak CallableObjectType jest ulepszeniem, ale nie jest wystarczająco dobra, aby komuś przekazać Intellisense to, czego oczekuje. Ponadto: ograniczenia na "F" w moim przykładzie są takie, że musi to być funkcja, która zwraca pustkę i pobiera T *. – scobi

0

W C++ 98 i C++ 03 odliczanie argumentów szablonów działa tylko z funkcjami (i metodami).

Nie sądzę, że obraz zmienił się w nowszych standardach.

1

Nie można wywnioskować typu na podstawie nazwy zagnieżdżonej: nie ma powodu, dla którego różne wystąpienia typu zewnętrznego nie będą definiować identycznego typu wewnętrznego. Można użyć using alias, choć:

template <typename T> 
using Function = auto (*)(T*) -> void; 

template <typename T> 
void Foo(Function<T>) { 
} 

Osobiście odradzam używając dowolnej z tym jednak: w praktyce wydaje się o wiele bardziej wskazane, aby rzeczywiście wziąć obiekt funkcji, które później pozwala na wykorzystanie obiektu z odpowiedni operator wywołania funkcji, który ma być używany. W przypadku wywołań zwrotnych często zdarza się, że trzeba przekazać pewne dane pomocnicze.Oznacza to, że trzeba albo użyć swobodną szablon lub jedną, która pobiera typu typ-usunięte, w zależności od tego, co chcesz zrobić dokładnie:

template <typename Fun> 
void Unconstrained(Fun fun) { 
} 
template <typename T> 
void TypeErased(std::function<void(T*)> fun) { 
} 

Wersja niewymuszony ma tę zaletę, że może potencjalnie inline wywołanie funkcji ma jednak tę wadę, że każdy typ obiektu funkcji tworzy nową instancję i że typy argumentów mogą się różnić. Wersja skasowana typowo musi działać podobnie do wirtualnego wywołania funkcji, ale istnieje tylko jedno utworzenie szablonu funkcji (oczywiście dla każdego argumentu: T).

Wprawdzie typu typ-skasowane wersji nie zostanie wyprowadzona z funkcji wskaźnika (lub jakikolwiek inny argument, który nie jest std::function<void(X*)>), czyli może chcesz mieć funkcję spedycja

template <typename T> 
void TypeErased(Function<T> fun) { 
    TypeErased(std::function<void(T)>(fun)); 
} 
Powiązane problemy