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/27Die fehlerverursachende Zeile ist zu finden in Zeile 200 von:
https://github.com/decimad/luabind-deboostified/blob/master/luabind/out_value_policy.hppDer 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
-
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!
-
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.
-
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.
-
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 = 1g++-7.1.0 schrieb:
A::x = 0; B::x = 0
A::x = 1; B::x = 0Wie 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 = 1Hier 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>(); }
-
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 classC
**. […]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.
-
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 classC
**. […]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?
-
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 kannA
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.