Ich habe gerade gesehen, wie Stephan T. Lavavej bei CppCon 2018
bei "Class Template Argument Deduction", wobei er um irgendwann nebenbei sagt:
In C++ fließen Informationen fast nie rückwärts ... Ich musste "fast" sagen, weil es ein oder zwei Fälle gibt, möglicherweise mehr, aber nur sehr wenige .
Trotz des Versuchs herauszufinden, auf welche Fälle er sich beziehen könnte, konnte ich mir nichts einfallen lassen. Daher die Frage:
In welchen Fällen gibt der C++ 17-Standard an, dass sich Typinformationen rückwärts ausbreiten?
Hier ist mindestens ein Fall:
struct foo {
template<class T>
operator T() const {
std::cout << sizeof(T) << "\n";
return {};
}
};
wenn Sie foo f; int x = f; double y = f;
eingeben, fließen die Informationen "rückwärts", um herauszufinden, was T
in operator T
ist.
Sie können dies in erweiterter Weise verwenden:
template<class T>
struct tag_t {using type=T;};
template<class F>
struct deduce_return_t {
F f;
template<class T>
operator T()&&{ return std::forward<F>(f)(tag_t<T>{}); }
};
template<class F>
deduce_return_t(F&&)->deduce_return_t<F>;
template<class...Args>
auto construct_from( Args&&... args ) {
return deduce_return_t{ [&](auto ret){
using R=typename decltype(ret)::type;
return R{ std::forward<Args>(args)... };
}};
}
also jetzt kann ich tun
std::vector<int> v = construct_from( 1, 2, 3 );
und es funktioniert.
Warum nicht einfach {1,2,3}
? Nun, {1,2,3}
Ist kein Ausdruck.
std::vector<std::vector<int>> v;
v.emplace_back( construct_from(1,2,3) );
was zugegebenermaßen etwas mehr Zauberei erfordert: Live-Beispiel . (Ich muss die deduzierte Rückgabe veranlassen, eine SFINAE-Prüfung von F durchzuführen, dann muss F SFINAE-freundlich sein. und Ich muss std :: initializer_list blockieren in deduce_return_t operator T.)
Stephan T. Lavavej hat den Fall, von dem er gesprochen hat, in einem Tweet erklärt :
Der Fall, an den ich gedacht habe, ist der, an den Sie die Adresse einer überladenen/mit Vorlagen versehenen Funktion abrufen können, und wenn sie zum Initialisieren einer Variablen eines bestimmten Typs verwendet wird, wird dadurch die gewünschte Variable eindeutig. (Es gibt eine Liste der eindeutigen Begriffe.)
beispiele hierfür finden Sie unter cppreference page on Address of overloaded function , ich habe nachfolgend ein paar Ausnahmen gemacht:
int f(int) { return 1; }
int f(double) { return 2; }
void g( int(&f1)(int), int(*f2)(double) ) {}
int main(){
g(f, f); // selects int f(int) for the 1st argument
// and int f(double) for the second
auto foo = []() -> int (*)(int) {
return f; // selects int f(int)
};
auto p = static_cast<int(*)(int)>(f); // selects int f(int)
}
Es ist auch nicht auf die Initialisierung eines konkreten Typs beschränkt. Es könnte auch nur aus der Anzahl der Argumente geschlossen werden
und liefert dieses Live-Beispiel :
void overload(int, int) {}
void overload(int, int, int) {}
template <typename T1, typename T2,
typename A1, typename A2>
void f(void (*)(T1, T2), A1&&, A2&&) {}
template <typename T1, typename T2, typename T3,
typename A1, typename A2, typename A3>
void f(void (*)(T1, T2, T3), A1&&, A2&&, A3&&) {}
int main () {
f(&overload, 1, 2);
}
was ich ein wenig ausarbeite mehr hier .
Ich glaube, dass beim statischen Casting überladener Funktionen der Fluss in die entgegengesetzte Richtung läuft wie bei der üblichen Überladungsauflösung. Also ist einer von denen rückwärts, denke ich.