Problem bei Name-Lookups mit namespace / templates



  • Hallo Leute,

    zunächst einmal: Mir fällt es schwer, mein Problem zu beschreiben. Scheinbar haben GCC-Versionen ab 5.3 Problemen Code in einem meiner Projekte zu übersetzen, da im Rahmen von Templates statt einer Member-Funktion eine normale Funktion mit gleichem Namen aufgerufen wird. Als fiele ohne weitere Qualifikation die Member-Funktion aus der Menge der Kandidaten. Ich habe dazu eine issue von mehreren Benutzern erhalten, diese ist hier auf github zu finden:
    https://github.com/decimad/luabind-deboostified/issues/27

    Die fehlerverursachende Zeile ist zu finden in Zeile 200 von:
    https://github.com/decimad/luabind-deboostified/blob/master/luabind/out_value_policy.hpp

    Der Aufruf der template-Member-Funktion "construct" scheint stattdessen bei der namespace-level Funktion "luabind::detail::construct" zu landen. Aber das ergibt für mich überhaupt keinen Sinn. Ist das eine "Besonderheit" der Template-Lookup-Regeln, welche zwecks Standard-Konformität zwischenzeitlich in den gcc- und clang-Compilern nachgerüstet wurde? Was könnte ich tun um das Problem zu vermeiden, ohne die Funktionen umzubenennen?

    Es tut mir ausdrücklich Leid, dass ich irgendwie für kein Minimal-Beispiel sorgen kann. 😞

    Viele Grüße,
    decimad


  • Mod

    clang-4.0 und 5.0 scheinen keine Probleme mit dem Code zu haben.
    Diese Holzhammermethode funktioniert:

    T& to_cpp(lua_State*, by_reference<T>, int)
                            {
                                    storage_.decltype(storage_)::template construct<T>();
    

    (weiter unten analog). Der Grund für das Verhalten von g++ dürfte darin liegen, dass storage_ einen abhängigen Typ hat, und daher erst nach der Instantiierung durchsucht werden kann. Ausserdem ist die Namensuche beim class-member-access für Templatespezialisierungen (6.5.4/1) zweistufig: erst wird die Klasse durchsucht (geht nicht vor Instantiierung), wenn dort nichts gefunden wird, ist der Kontext des gesamten Ausdrucks dran (mithin unqualifizierter Lookup im lokalen Scope) und findet dann luabind::detail::construct.

    minimaler Code:

    template <typename T, typename U>
    struct foo {};
    
    template <typename T>
    struct bar {
        template <typename U>
        void foo();
    };
    
    template <typename T>
    void f(bar<T> v) {
        v.template foo<T>();
    }
    


  • Vielen Dank camper, das war eine extrem große Hilfe!
    Ich denke, ich muss mich einmal wirklich richtig ausgiebig mit den Lookup-Regeln auseinandersetzen. Aber wirklich akribisch.

    Danke nochmal!


  • Mod

    Mein Gefühl sagt mir, dass g++ wahrscheinlich unrecht hat: wenn wirklich ein non-member Template gemeint wäre, bedürfte es ja nicht des Schlüsselwortes template. Vielleicht hat ja Arcoth Lust, das zu recherchieren.


  • Mod

    Eine andere Möglichkeit, zu verhindern, dass g++ die falsche Entität benutzen will, besteht darin, das non-member Klassentemplate zu verdecken.
    Beispielsweise:

    template<class T>
                            T& to_cpp(lua_State*, by_reference<T>, int)
                            {
                                    using construct = void; // dummy declaration in order to shadow construct class template in surrounding scope
                                    storage_.template construct<T>();
    

    Etwas fragwürdig ist es nat. schon, überhaupt eine derartige Namenskollision zu haben.


  • Mod

    Je länger ich darüber nachdenke, um so mehr bin ich davon überzeugt, dass g++ unrecht hat. Mein Hinweis oben auf 6.4.5/1 ist unbeachtlich, da die Voraussetzungen nicht erfüllt sind

    N4659 schrieb:

    6.4.5 Class member access [basic.lookup.classref]
    1 In a class member access expression (8.2.5), if the . or -> token is immediately followed by an identifier
    followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template
    argument list (17.2) or a less-than operator. The identifier is first looked up in the class of the object
    expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression
    and shall name a class template.

    Hervorhebung durch mich.

    Hier ein pathologisches Beispiel, dass kompiliert und das unterschiedliche Verhalten der Compiler aufzeigt:

    template <typename>
    struct A {
        int x;
    };
    template <typename>
    struct B {
        int x;
    };
    
    template <typename T>
    struct bar : A<T>, B<T> {
        template <typename U>
        using A = B<T>;
    };
    
    template <typename T>
    void foo(bar<T>& v) {
        v.template A<T>::x = 1;
    }
    
    #include <iostream>
    int main() {
        bar<int> v{};
        std::cout << "A::x = " << static_cast<A<int>&>(v).x << "; B::x = " << static_cast<B<int>&>(v).x << '\n';
        foo(v);
        std::cout << "A::x = " << static_cast<A<int>&>(v).x << "; B::x = " << static_cast<B<int>&>(v).x << '\n';
    }
    

    clang++-5.0 SVN schrieb:

    A::x = 0; B::x = 0
    A::x = 0; B::x = 1

    g++-7.1.0 schrieb:

    A::x = 0; B::x = 0
    A::x = 1; B::x = 0

    Wie verhält sich Visual C++ hier?
    Vergleiche die Variante ohne Templates:

    struct A {
        int x;
    };
    struct B {
        int x;
    };
    
    struct bar : A, B {
        using A = B;
    };
    
    void foo(bar& v) {
        v.A::x = 1;
    }
    
    #include <iostream>
    int main() {
        bar v{};
        std::cout << "A::x = " << static_cast<A&>(v).x << "; B::x = " << static_cast<B&>(v).x << '\n';
        foo(v);
        std::cout << "A::x = " << static_cast<A&>(v).x << "; B::x = " << static_cast<B&>(v).x << '\n';
    }
    

    Hier stimmen beide Compiler überein:

    A::x = 0; B::x = 0
    A::x = 0; B::x = 1

    Hier noch eine 😡 Variante, um den ursprünglichen Fehler zu beseitigen

    template<class construct> // !!!
                            construct& to_cpp(lua_State*, by_reference<construct>, int)
                            {
                                    storage_.template construct<construct>();
                                    return storage_.template get<construct>();
                            }
    

  • Mod

    Ausserdem ist die Namensuche beim class-member-access für Templatespezialisierungen (6.5.4/1) zweistufig:

    Dieser Paragraph trifft nicht zu. Obiger Code enthält das template keyword.

    Was wir hier betrachten ist der seit langem bekannter GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54769, der letzte Woche gefixt wurde. :p Es gibt eine ganze Batterie von bug reports für verschiedene Variationen dieses Bugs in verschiedenen Implementierungen, e.g. für Clang siehe #11856.

    Dass es sich um einen Bug handelt (und nicht um defektes wording) stellen wir fest, wenn wir den Rest des Abschnitts anschauen.

    <a href= schrieb:

    [basic.lookup.classref]/2">If the id-expression in a class member access is an unqualified-id, and the type of the object expression is of a class type C , **the unqualified-id is looked up in the scope of class C **. […]

    Der von vielen anderen Diskussionen und einigen DRs behandelte dual lookup, also der lookup sowohl innerhalb der Klasse als auch im Kontext der postfix-expression, trifft nur zu, wenn das template keyword eben nicht zur Disambiguierung eingesetzt wird.


  • Mod

    Arcoth schrieb:

    Ausserdem ist die Namensuche beim class-member-access für Templatespezialisierungen (6.5.4/1) zweistufig:

    Dieser Paragraph trifft nicht zu. Obiger Code enthält das template keyword.

    Jep.

    Arcoth schrieb:

    Dass es sich um einen Bug handelt (und nicht um defektes wording) stellen wir fest, wenn wir den Rest des Abschnitts anschauen.

    <a href= schrieb:

    [basic.lookup.classref]/2">If the id-expression in a class member access is an unqualified-id, and the type of the object expression is of a class type C , **the unqualified-id is looked up in the scope of class C **. […]

    ODer von vielen anderen Diskussionen und einigen DRs behandelte dual lookup, also der lookup sowohl innerhalb der Klasse als auch im Kontext der postfix-expression, trifft nur zu, wenn das template keyword eben nicht zur Disambiguierung eingesetzt wird.

    Einverstanden.

    Mein pathologischer Code betrifft ein anderes Problem. Ist A in

    template <typename T>
    void foo(bar<T>& v) {
        v.template A<T>::x = 1;
    }
    

    abhängig oder nicht?


  • Mod

    camper schrieb:

    Mein pathologischer Code betrifft ein anderes Problem. Ist A in

    template <typename T>
    void foo(bar<T>& v) {
        v.template A<T>::x = 1;
    }
    

    abhängig oder nicht?

    Soweit ich sehe trifft auf Deinen Code Paragraph 4 zu (zur Definitionszeit), d.h. GCC 7.1 hat hier u.U. Recht, da der alternative Zweig genommen werden muss ( v ist abhängig, und damit kann A nicht in der zugehörigen Klasse gesucht werden). Diese Exegese des wordings wird auch von den Beteiligten der oben verlinkten PRs geteilt.

    Genau wie schon oben von Dir demonstriert kann man hier das Verhalten durch eine entsprechende Qualifizierung anpassen. Interessanter ist der Fall, in dem das Klassentemplate A umbenannt wird. Obiger Argumentation zufolge hätten wir nun eine Fehlermeldung, und das ist tatsächlich genau was GCC 7.1 uns gibt. Clang hält weiter an der Anwendung des lookups zur Instantiierungszeit fest. Jetzt bleibt nur noch die Frage, ob man einen PR für Clang öffnet, oder einen Thread in std-discussion.


Anmelden zum Antworten