Unterschiedliche Funktionsaufrufe in Abhängigkeit von Lambda-Funktion-Parameter
-
Damit es auch vollständig ist:
Hier ist eine Möglichkeit:
#include <iostream> #include <vector> #include <functional> template<typename TFunc> struct helper : helper<decltype(&TFunc::operator())>{ }; template<typename ClassType, typename ReturnType, typename Arg> struct helper<ReturnType(ClassType::*)(Arg) const>{ template<typename T> void operator()(T t, const std::vector<Arg>& arg){ for(auto a:arg){ t(a); } } }; template<typename ClassType, typename ReturnType, typename Arg> struct helper<ReturnType(ClassType::*)(const std::vector<Arg>&) const>{ template<typename T> void operator()(T t, const std::vector<Arg>& arg){ t(arg); } }; template<typename T, typename TFunc> void doit(const std::vector<T>& vec, TFunc f){ typedef decltype(f) function_type; helper<function_type> help; help(f, vec); } int main(){ std::vector<int> vec; vec.push_back(1); vec.push_back(2); vec.push_back(3); auto f1 = [](int i){ std::cout << "int " << std::endl; }; auto f2 = [](const std::vector<int>& i){ std::cout << "vec " << std::endl; }; doit(vec, f1); doit(vec, f2); }Gruß,
XSpilleDANKE NOCHMAL!
-
Als (schönere) Alternative mit Verwendung des function_traits:
#include <iostream> #include <vector> #include <functional> template <typename T> struct function_traits : public function_traits<decltype(&T::operator())>{}; // For generic types, directly use the result of the signature of its 'operator()' template <typename ClassType, typename ReturnType, typename... Args> struct function_traits<ReturnType(ClassType::*)(Args...) const> // we specialize for pointers to member function { enum { arity = sizeof...(Args) }; // arity is the number of arguments. typedef ReturnType result_type; template <size_t i> struct arg { typedef typename std::tuple_element<i, std::tuple<Args...>>::type type; // the i-th argument is equivalent to the i-th tuple element of a tuple // composed of those arguments. }; }; template<typename T> struct typeinfo{}; template<typename T, typename TFunc> void doit(const typeinfo<T>&, const std::vector<T>& vec, TFunc lambda){ for(auto i : vec){ lambda(i); } } template<typename T, typename TFunc> void doit(const typeinfo<const std::vector<T>&>&, const std::vector<T>& vec, TFunc lambda){ lambda(vec); } template<typename T, typename TFunc> void doit(const std::vector<T>& vec, TFunc lambda){ typedef function_traits<decltype(lambda)> traits; typedef typename traits::template arg<0>::type param_type; doit(typeinfo<param_type>(), vec, lambda); } int main(){ std::vector<int> vec; vec.push_back(1); vec.push_back(2); vec.push_back(3); auto f1 = [](int i){ std::cout << "int " << std::endl; }; auto f2 = [](const std::vector<int>& i){ std::cout << "vec " << std::endl; }; doit(vec, f1); doit(vec, f2); }Gruß,
XSpille
-
Das ist aber nicht schön.
Schöner ist das hier:
template<class T, class...Dummy> using alias = T; template<typename T, typename Func> auto doit(const std::vector<T>& vec, Func func) -> alias<void, decltype( func(vec[0]) )> // SFINAE expression test { for(auto& i : vec){ func(i); } } template<typename T, typename Func> auto doit(const std::vector<T>& vec, Func func) -> alias<void, decltype( func(vec) )> // SFINAE expression test { func(i); }Getestet hab ich's jetzt aber nicht.
Jedenfalls sollte man sich nicht auf so etwas wie function_traits verlassen, da der Funktionsaufrufoperator des Funktors mehrfach überladen sein könnte. Wichtig ist ja nur, ob ein bestimmter Aufruf funktioniert oder eben nicht. Und das kann man dann einfach testen.
-
Danke krümelkacker!
Nachdem ich endlich mein Macports aktualisiert habe um gcc 4.7 zu installieren,
da es template aliasing unterstützt, stelle ich nun fest, dass es leider wegen einer redefinition nicht so funktioniert.#include <iostream> #include <vector> template<class T, class... Dummy> using alias = T; template<typename T, typename Func> auto doit(const std::vector<T>& vec, Func func) -> alias<void, decltype( func(vec) )> // SFINAE expression test { } template<typename T, typename Func> auto doit(const std::vector<T>& vec, Func func) -> alias<void, decltype( func(vec[0]) )> // SFINAE expression test { } int main(){ std::vector<int> vec; auto f1 = [](int i){ std::cout << "int " << std::endl; }; auto f2 = [](const std::vector<int>& i){ std::cout << "vec " << std::endl; }; //doit(vec, f1); //doit(vec, f2); }test.cpp:13:6: error: redefinition of 'template<class T, class Func> alias<void, decltype (func(vec[0]))> doit(const std::vector<T>&, Func)'
test.cpp:8:6: error: 'template<class T, class Func> alias<void, decltype (func(vec))> doit(const std::vector<T>&, Func)' previously declared hereAlso generell verstehe ich, dass der Compiler meckert, weil beide Funktionen ja die gleichen Signaturen haben.
Gibt es dafür eine (einfache) Lösung, um diesen Fehler zu beseitigen?
Gruß,
XSpilleEDIT: gcc 4.8 meckert auch wegen einer redefinition
-
SFINAE ist schon das richtige Stichwort, nur muss es eben über die Parameter gemacht werden.
#include <iostream> #include <vector> template<typename T, typename Func> void doit(const std::vector<T>& vec, Func func, decltype(func(vec))* = nullptr) { func(vec); } template<typename T, typename Func> void doit(const std::vector<T>& vec, Func func, decltype(func(vec[0]))* = nullptr) { for(const T& t : vec) func(t); } int main(){ std::vector<int> vec { 1, 2, 3 }; auto f1 = [](int i){ std::cout << "int " << std::endl; }; auto f2 = [](const std::vector<int>& i){ std::cout << "vec " << std::endl; }; doit(vec, f1); doit(vec, f2); }
-
ipsec schrieb:
SFINAE ist schon das richtige Stichwort, nur muss es eben über die Parameter gemacht werden.
Das ist ein Möglichkeit. Man kann auch die Templateparameter variieren
template<typename T, typename Func, typename = decltype(std::declval<Func>()(std::declval<const std::vector<T>&>()))> void doit(const std::vector<T>& vec, Func func) { func(vec); } template<typename T, typename Func, typename = void, typename = decltype(std::declval<Func>()(std::declval<const std::vector<T>&>()[0]))> void doit(const std::vector<T>& vec, Func func) { for(const T& t : vec) func(t); }Aber auch mit dem Rückgabewert funktioniert es, sofern man das Aliastemplate geeignet schreibt (bzw. gleich darauf verzichtet).
template <typename T, typename...> struct helper { using type = T; }; template<class T, class... Dummy> using alias = typename helper<T, Dummy...>::type; template<typename T, typename Func> auto doit(const std::vector<T>& vec, Func func) -> alias<void, decltype( func(vec) )> { func(vec); } template<typename T, typename Func> auto doit(const std::vector<T>& vec, Func func) -> alias<void, decltype( func(vec[0]) )> { for(const T& t : vec) func(t); }Das Problem mit dem Aliastemplate allein liegt darin, dass die Substitution des Aliastyps unmittelbar bei in der Definition des Funktionstemplates stattfindet (und dieser Typ dann offenbar nur von T=void abhängt), während bei einem Klassentemplate die Substitution erst bei der Instantiierung durchgeführt werden kann.
Also im Prinzip eager vs. lazy evaluation.
-
DANKE ipsec für die Lösung, die ich favorisiere

DANKE camper für Alternativen dazu

-
Eine Frage hab ich doch noch...
Gibt es Vor-/Nachteile, ob man SFINAE über einen normalen Parameter, einen Template-Parameter oder Rückgabetyp macht (abgesehen von Notationsvorzügen)?
-
XSpille schrieb:
Eine Frage hab ich doch noch...
Gibt es Vor-/Nachteile, ob man SFINAE über einen normalen Parameter, einen Template-Parameter oder Rückgabetyp macht (abgesehen von Notationsvorzügen)?
Ich zähle einfach mal die Nachteile jeder Variante gegenüber den anderen beiden auf, wie sie mri in den Sinn kommen:
1. Über zusätzlichen Funktionsparameter: ändert die Funktionssignatur, nicht möglich bei Operatorüberladung (außer ggf. () und Postfix ++/--), weil die die Parameterzahl feststeht.
2. über Rückgabetyp: nicht möglich bei Konstruktoren/Konvertierungsoperatoren
3. Über Defaultargumente für Templateparameter: benötigt C++11-Unterstützung, benötigt zusätzliche Dummyparameter, um die Templates auseinanderzuhalten (Defaultargumente sind nicht Teil der Signatur).Im Allgemeinen sollte die konkrete Wahl keine Rolle spielen, also immer das wählen, was am besten zu lesen ist.
-
camper schrieb:
XSpille schrieb:
Eine Frage hab ich doch noch...
Gibt es Vor-/Nachteile, ob man SFINAE über einen normalen Parameter, einen Template-Parameter oder Rückgabetyp macht (abgesehen von Notationsvorzügen)?
Ich zähle einfach mal die Nachteile jeder Variante gegenüber den anderen beiden auf, wie sie mri in den Sinn kommen:
1. Über zusätzlichen Funktionsparameter: ändert die Funktionssignatur, nicht möglich bei Operatorüberladung (außer ggf. () und Postfix ++/--), weil die die Parameterzahl feststeht.
2. über Rückgabetyp: nicht möglich bei Konstruktoren/Konvertierungsoperatoren
3. Über Defaultargumente für Templateparameter: benötigt C++11-Unterstützung, benötigt zusätzliche Dummyparameter, um die Templates auseinanderzuhalten (Defaultargumente sind nicht Teil der Signatur).Im Allgemeinen sollte die konkrete Wahl keine Rolle spielen, also immer das wählen, was am besten zu lesen ist.
DANKE camper für diese Analyse!
