Jak mogę zapobiec zgadywaniu przez C ++ drugiego argumentu szablonu?

26

Korzystam z biblioteki C ++ ( strf ), która gdzieś w niej zawiera następujący kod:

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) { /* ... */ }
}

Teraz chcę użyć strf::range<const char*>(some_char_ptr, some_char_ptr + some_length)w moim kodzie. Ale jeśli to zrobię, pojawia się następujący błąd (z NVCC 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)

Biblioteka kod może być prawdopodobnie zmieniła tego uniknąć (np za pomocą:

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)

upewnić Rangesię, że nie jest wskaźnikiem); ale nie mogę teraz wprowadzić tej zmiany. Zamiast tego chcę w jakiś sposób wskazać kompilatorowi, że naprawdę mam na myśli tylko jeden argument szablonu, nie jeden określony, a drugi wywnioskowany.

Czy mogę to zrobić?

Byłbym wdzięczny za odpowiedzi dla C ++ 11 i C ++ 14; Odpowiedzi w C ++ 17 dotyczące przewodników po dedukcji są mniej przydatne, ale jeśli masz, opublikuj je (dla przyszłych wersji NVCC ...)


Aktualizacja: sama biblioteka strf została zaktualizowana w celu obejścia tej sytuacji, ale pytanie jest zadane.

einpoklum
źródło
1
Zgaduję, że przekazanie niestandardowego iteratora, który cienko otacza, char*ale czy to nie jest rozwiązanie?
Konrad Rudolph
1
@KonradRudolph: To obejście, ale nie odpowiada na moje pytanie. Właściwie mam już inne obejście (specyficzne dla tego, co znajduje się w /*...*/), ale chciałbym tu pójść główną drogą.
einpoklum
1
W takim przypadku moja (domniemana) odpowiedź brzmi „nie można tego zrobić”, niestety. Aby być uczciwym, nie jestem pewien, czy zaakceptowałbym sugerowane obejście w moim własnym kodzie.
Konrad Rudolph
Wyjaśnij: czy potrzebujesz ogólnego rozwiązania, które zawsze zadziałałoby, aby rozróżnić wywołanie przeciążenia szablonu jednym lub dwoma parametrami, czy chcesz tylko rozwiązanie specyficzne dla tego przypadku?
orzech
@walnut: Ogólne rozwiązanie byłoby lepsze; mój konkretny scenariusz jest głównie motywacją do rozwiązania problemu.
einpoklum

Odpowiedzi:

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

Następnie zadzwoń range1zamiast strf::range.

range1_ptr<T>(...)zawsze można użyć do jawnego wywołania szablonu, biorąc jeden argument szablonu, ale nie robi on żadnego odliczenia od argumentów. range1replikuje odliczenie od oryginalnego strf::rangeszablonu.

Działa to, ponieważ [temp.deduct.funcaddr] / 1 mówi, że dedukcja argumentu szablonu przy przyjmowaniu adresu funkcji bez typu docelowego konwersji jest wykonywana na każdym szablonie funkcji kandydującej, tak jakby listy parametrów i argumentów hipotetycznego wywołania były pusty. Zatem drugiego argumentu szablonu nie można wywnioskować dla drugiego przeciążenia dwoma parametrami szablonu. Pozostał tylko kandydat na pierwsze przeciążenie, które zostanie wybrane jako cel wskaźnika funkcji.

Dopóki nie istnieje drugi szablon funkcji kandydata, dla którego można utworzyć prawidłowy identyfikator szablonu z tylko jednym argumentem, range1_ptrzawsze można go użyć do wywołania szablonu funkcji przyjmującego jeden argument jednoznacznie. W przeciwnym razie utworzenie wystąpienia range1_ptrspowoduje błąd z powodu niejednoznaczności.

orzech włoski
źródło
Czy nie będzie dwuznaczności strf::range<T>?
einpoklum
1
@einpoklum Kompiluje się dobrze na GCC i Clang. Nie sprawdziłem standardu, ale zdziwiłbym się, gdyby miało to być dwuznaczne.
orzech
Może powinieneś zmienić nazwę funkcji na pretty_please_with_sugar_on_top()? ... C ++ może być czasem tak dziwny ...
einpoklum
Udało Ci się odsunąć zaakceptowaną odpowiedź :-P
einpoklum
11

Co powiesz na przejście przez using?

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

tfp x = &strf::range;

char const * a = "abcd";

(*x)(a, a+2);
max66
źródło
A to się kompiluje? Druga linia wygląda szczególnie podejrzanie.
einpoklum
@einpoklum - śmieszne, prawda?
max66
@einpoklum - niestety nie jest ogólnym rozwiązaniem; działa w tym przypadku, ponieważ (jeśli się nie mylę) tylko pierwsza range()wersja jest kompatybilna tpf; inny przypadek może być inny.
max66
@einpoklum - w drugim wierszu możesz także wyjaśnić parametr template ( tfp x = &strf::range<char const *>;); w ten sposób przypuszczam, że masz ogólne rozwiązanie, prawie równoważne rozwiązaniu z orzecha włoskiego
max66
0

Rozwiązaniem jest

1) przede wszystkim należy podać typ drugiego argumentu, np (char *)(some_char_ptr + some_length)

2) nie używaj constobu, działa to dobrze:

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

Można spróbować wymienić (char *)się (const char *)w lewo lub w prawo, to nadal działa.

tontonCD
źródło
Jest to dość brzydkie, jeśli argumenty wskazują na constdane.
aschepler
1. Nie ma drugiego argumentu. Chcę szablon z jednym argumentem. 2. Ciekawy hack! -1 dla pierwszej sugestii i +1 dla drugiej :-P
einpoklum