wake-up-neo.com

Wann fließen Typinformationen in C ++ zurück?

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?

90
Massimiliano

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)
}

Michael Park fügt hinz :

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 .

30
Shafik Yaghmour

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.

19
jbapple