2016-07-28 19 views
3

Ich habe das folgende Stück Code:Warum kann nicht clang ++ den Typ einer Karte von Lambdas ableiten?

enum RelationalOperator { LT, LTE, EQ, GTE, GT }; 
std::map<RelationalOperator, bool (*)(const Point&, const Point&)> ops = { 
    { GTE, [](const Point& a, const Point& b) { return a >= b; } }, 
    { LTE, [](const Point& a, const Point& b) { return a <= b; } }, 
    { EQ, [](const Point& a, const Point& b) { return a == b; } }, 
    { GT, [](const Point& a, const Point& b) { return a > b; } }, 
    { LT, [](const Point& a, const Point& b) { return a < b; } }, 
}; 

Dieser Code innerhalb einer Vorlage und Point ist ein Template-Parameter.

Ich versuchte, den Typ der Variablen ops mit auto aber Clang ++ sagt zu ersetzen:

src/utils.hpp:47:10: error: cannot deduce actual type for variable 'ops' with type 'auto' from initializer list 

Warum das so ist? Ich dachte, dass das Schlüsselwort auto für diese Art von Situationen war, wo der Typ lang und ziemlich offensichtlich ist.

+3

Es ist nicht * das * offensichtlich, dass Sie eine 'map' und keine' multimap' oder ein Array von Strukturen wollen. –

+0

Bitte geben Sie einen [mcve] (http://stackoverflow.com/help/mcve) für den eigentlichen Code ein, der ein Problem verursacht. –

+0

@BoPersson Mein Fehler, ich habe nicht berücksichtigt, dass andere Objekte dieselbe Initialisiererliste haben können! – rubik

Antwort

4

Zunächst einmal jedes Lambda seine eigene Art hat, so dass eine Reihe von verschiedenem Lambda-Ausdrücke gegeben, können Sie sie nicht in einen Faktor einzelner Typ ohne manuelles Gießen (typischerweise eingebettet in std::function<R(Args...)> Objekte).

Dann, wenn Sie schreiben, eine solche Initialisierung:

enum RelationalOperator { LT, LTE, EQ, GTE, GT }; 
std::map<RelationalOperator, bool (*)(const Point&, const Point&)> ops = { 
    { GTE, [](const Point& a, const Point& b) { return a >= b; } }, 
    { LTE, [](const Point& a, const Point& b) { return a <= b; } }, 
    { EQ, [](const Point& a, const Point& b) { return a == b; } }, 
    { GT, [](const Point& a, const Point& b) { return a > b; } }, 
    { LT, [](const Point& a, const Point& b) { return a < b; } }, 
}; 

Was wirklich passiert? Es ruft den std::initializer_list Konstruktor von std::map<RelationalOperator, bool (*)(const Point&, const Point&)> auf. Es ist auch in der Lage zu folgern, dass die gegebene verspannt Ausdruck ist ein Initialisiererliste für eine solche Karte:

std::initializer_list<std::pair<RelationalOperator, bool (*)(Point const&, Point const&)>> 

Dann wird eine implizite Konvertierung für Ihre lambdas auftreten.

Nun, wenn Sie schreiben, statt:

auto ops = { 
    { GTE, [](const Point& a, const Point& b) { return a >= b; } }, 
    { LTE, [](const Point& a, const Point& b) { return a <= b; } }, 
    { EQ, [](const Point& a, const Point& b) { return a == b; } }, 
    { GT, [](const Point& a, const Point& b) { return a > b; } }, 
    { LT, [](const Point& a, const Point& b) { return a < b; } }, 
}; 

Es kann nicht herausfinden, welche Art von Objekt durch den verspannten Ausdruck dargestellt wird (die T im std::initializer_list<T>). Das ist ganz explizit in gcc's error message:

main.cpp:8:29: error: unable to deduce 'std::initializer_list<auto>' from '{{1, 2}, {3, 4}}' 

    auto x = {{1, 2}, {3, 4}}; 

          ^

main.cpp:8:29: note: couldn't deduce template parameter 'auto' 
5

auto funktioniert nicht mit Initialisierungslisten. Das gleiche Initialisiererliste verwendet werden, um eine Reihe von anderen Arten zu initialisieren, zum Beispiel:

std::map<int, bool (*)(const Point&, const Point&)> 
std::multimap<RelationalOperator, bool (*)(const Point&, const Point&)> 
std::vector<std::pair<int, bool (*)(const Point&, const Point&)>> 
1

Sehen Sie sich auch STL eingebauten im Vergleich functors mit nämlich std::equal_to, std::less_equal, std::greater_equal, std::less und std::greater alle in <functional> Header-Datei.

Z. B .:

#include <functional> 
#include <map> 

struct Point { 
    bool operator < (const Point &) const { /* actual implementation */ } 
    bool operator <= (const Point &) const { /* actual implementation */ } 
    bool operator > (const Point &) const { /* actual implementation */ } 
    bool operator >= (const Point &) const { /* actual implementation */ } 
    bool operator == (const Point &) const { /* actual implementation */ } 
    /* other stuff */ 
}; 

int main() { 
    enum RelationalOperator { LT, LTE, EQ, GTE, GT }; 
    std::map<RelationalOperator, std::function<bool(const Point&, const Point&)>> ops = { 
    {GTE, std::greater_equal<Point>()}, 
    {LTE, std::less_equal<Point>()}, 
    {EQ, std::equal_to<Point>()}, 
    {GT, std::greater<Point>()}, 
    {LT, std::less<Point>()}, 
    }; 
} 
+0

Ich wusste nichts von denen, danke! – rubik

3

Ich dachte, dass das Schlüsselwort auto für diese Art von Situationen war, wo der Typ lang und ziemlich offensichtlich.

Der Typ ist überhaupt nicht offensichtlich. Jeder Lambda-Ausdruck erzeugt einen eindeutigen anonymen Verschlusstyp, so dass jedes Element der Initialisiererliste hat eine andere Art:

auto ops = { 
    { GTE, lambda_type_1 }, 
    { LTE, lambda_type_2 }, 
    { EQ, lambda_type_3 }, 
    { GT, lambda_type_4 }, 
    { LT, lambda_type_5 }, 
}; 

Jeder verspannt initializer nichts gemein hat. Da ist nichts offensichtlich.

Wenn Sie die std::map initialisieren, gibt es einen Konstruktor, der std::initializer_list<value_type> nimmt, und der Compiler kann jeden Initialisierer in diesen Typ konvertieren. Wenn Sie die Karte mit ersetzen, gibt es keine Anhaltspunkte für den Compiler, um herauszufinden, welchen Typ Sie von einer Liste nicht verwandter Typen erwarten.