Wie kann ich verhindern, dass C ++ ein zweites Vorlagenargument errät?

26

Ich verwende eine C ++ - Bibliothek ( strf ), die irgendwo darin den folgenden Code enthält:

namespace strf {
template <typename ForwardIt>
inline auto range(ForwardIt begin, ForwardIt end) { /* ... */ }

template <typename Range, typename CharT>
inline auto range(const Range& range, const CharT* sep) { /* ... */ }
}

Jetzt möchte ich strf::range<const char*>(some_char_ptr, some_char_ptr + some_length)in meinem Code verwenden. In diesem Fall wird jedoch der folgende Fehler angezeigt (mit dem NVCC von CUDA 10.1):

error: more than one instance of overloaded function "strf::range" matches the argument list:
            function template "auto strf::range(ForwardIt, ForwardIt)"
            function template "auto strf::range(const Range &, const CharT *)"
            argument types are: (util::constexpr_string::const_iterator, util::constexpr_string::const_iterator)

Der Bibliothekscode kann wahrscheinlich geändert werden, um dies zu vermeiden (z. B. mit:

inline auto range(const typename std::enable_if<not std::is_pointer<typename std::remove_cv<Range>::type>::value, Range &>::type range, const CharT* sep)

zu gewährleisten Rangeist kein Zeiger); Aber ich kann das jetzt nicht ändern. Stattdessen möchte ich dem Compiler irgendwie anzeigen, dass ich wirklich nur ein Vorlagenargument haben möchte, nicht eines angegeben und ein anderes abgeleitet.

Kann ich das machen?

Würde mich über Antworten für C ++ 11 und C ++ 14 freuen; C ++ 17-Antworten mit Abzugsleitfäden sind weniger relevant. Wenn Sie jedoch eine haben, veröffentlichen Sie diese bitte (für zukünftige NVCC-Versionen ...).


Update: Die strf-Bibliothek selbst wurde aktualisiert, um diese Situation zu umgehen, aber die Frage bleibt wie gestellt.

einpoklum
quelle
1
Ich vermute, einen benutzerdefinierten Iterator zu übergeben, der einen dünn umschließt, char*aber ist einer keine Lösung?
Konrad Rudolph
1
@KonradRudolph: Das ist eine Problemumgehung, beantwortet aber meine Frage nicht. Ich habe tatsächlich bereits eine andere Problemumgehung (speziell für das, was in /*...*/ enthalten ist), aber ich würde gerne hier die Hauptstraße nehmen.
Einpoklum
1
In diesem Fall lautet meine (erratene) Antwort leider "nicht möglich". Um fair zu sein, bin ich mir nicht sicher, ob ich meine vorgeschlagene Problemumgehung in meinem eigenen Code akzeptieren würde.
Konrad Rudolph
Nur zur Klarstellung: Möchten Sie eine allgemeine Lösung, die immer dazu dient, einen Aufruf zwischen Vorlagenüberladungen mit einem oder zwei Parametern zu unterscheiden, oder möchten Sie nur eine für diesen Fall spezifische Lösung?
Walnuss
@walnut: Allgemeine Lösung wäre besser; Mein spezifisches Szenario ist hauptsächlich die Motivation für das Problem.
Einpoklum

Antworten:

16
template<typename T>
inline constexpr auto range1_ptr = strf::range<T>;

template<typename T>
inline decltype(auto) range1(T begin, T end) {
    return range1_ptr<T>(begin, end);
}

Dann rufen Sie range1statt strf::range.

range1_ptr<T>(...)kann immer verwendet werden, um die Vorlage explizit mit einem Vorlagenargument aufzurufen, führt jedoch keinen Abzug von den Argumenten durch. range1Repliziert den Abzug von der Originalvorlage strf::range.

Dies funktioniert, weil [temp.deduct.funcaddr] / 1 besagt, dass die Ableitung von Vorlagenargumenten, wenn die Adresse einer Funktion ohne der Konvertierung verwendet wird, für jede Kandidatenfunktionsvorlage so erfolgt, als ob die Parameter- und Argumentlisten eines hypothetischen Aufrufs wären leeren. Daher kann das zweite Vorlagenargument nicht für die zweite Überladung mit zwei Vorlagenparametern abgeleitet werden. Der einzige verbleibende Kandidat ist die erste Überladung, die als Ziel des Funktionszeigers ausgewählt wird.

Solange es keine zweite Kandidatenfunktionsvorlage gibt, für die eine gültige Vorlagen-ID mit nur einem Argument gebildet werden range1_ptrkann , kann immer verwendet werden, um die Funktionsvorlage mit einem Argument eindeutig aufzurufen. Andernfalls führt die Instanziierung von range1_ptraufgrund von Mehrdeutigkeiten zu einem Fehler.

Nussbaum
quelle
Wird es keine Unklarheiten geben strf::range<T>?
Einpoklum
1
@einpoklum Es kompiliert gut auf GCC und Clang. Ich habe den Standard nicht überprüft, wäre aber überrascht, wenn das nicht eindeutig sein soll.
Walnuss
Vielleicht sollten Sie den Funktionsnamen in ändern pretty_please_with_sugar_on_top()? ... C ++ kann manchmal so komisch sein ...
einpoklum
Sie haben es geschafft, die akzeptierte Antwort zu entfernen :-P
einpoklum
11

Was ist mit einem Durchgang durch ein using?

using tfp = void(*)(char const *, char const *);

tfp x = &strf::range;

char const * a = "abcd";

(*x)(a, a+2);
max66
quelle
Und das kompiliert? Die zweite Zeile sieht besonders verdächtig aus.
Einpoklum
@einpoklum - lustig, nicht wahr?
Max66
@einpoklum - ist leider keine allgemeine Lösung; funktioniert in diesem Fall, weil (wenn ich mich nicht irre) nur die erste range()Version kompatibel ist tpf; anderer Fall kann anders sein.
Max66
@einpoklum - In der zweiten Zeile können Sie auch den Vorlagenparameter ( tfp x = &strf::range<char const *>;) erläutern . Auf diese Weise haben Sie vermutlich eine allgemeine Lösung, die fast der der Walnuss entspricht
max66
0

Eine Lösung ist

1) Zunächst sollten Sie den Typ für das zweite Argument angeben, z (char *)(some_char_ptr + some_length)

2) nicht constfür beide verwenden, das funktioniert gut:

strf::range((char *)some_char_ptr, (char *)(some_char_ptr + some_length));

Sie können versuchen , zu ersetzen , (char *)mit (const char *)links oder rechts an, es funktioniert immer noch.

tontonCD
quelle
Dies ist ziemlich hässlich, wenn die Argumente auf constDaten verweisen .
Aschepler
1. Es gibt kein zweites Argument. Ich möchte die Vorlage mit einem Argument. 2. Interessanter Hack! -1 für den ersten Vorschlag und +1 für den zweiten :-P
einpoklum