27

I'm using a C++ library (strf) which, somewhere within it, has the following code:

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

Now, I want to use strf::range<const char*>(some_char_ptr, some_char_ptr + some_length) in my code. But if I do so, I get the following error (with CUDA 10.1's NVCC):

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)

The library code can probably be changed to avoid this (e.g. using:

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)

to ensure Range is not a pointer); but I can't make that change right now. Instead, I want to somehow indicate to the compiler that I really really mean to only have one template argument, not one specified and another one deduced.

Can I do that?

Would appreciate answers for C++11 and C++14; C++17 answers involving deduction guides are less relevant but if you have one, please post it (for future NVCC versions...)


Update: The strf library itself has been updated to circumvent this situation, but the question stands as asked.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 1
    I’m guessing passing a custom iterator that thinly wraps a `char*` but isn’t one is not a solution? – Konrad Rudolph Dec 30 '19 at 23:31
  • 1
    @KonradRudolph: That's a workaround, but doesn't answer my question. I actually already have another workaround (specific to what's in the /*...*/), but I'd like to take the high road here. – einpoklum Dec 30 '19 at 23:33
  • 1
    In that case my (guessed) answer is “cannot be done”, unfortunately. To be fair I’m not sure I would accept my suggested workaround in my own code. – Konrad Rudolph Dec 30 '19 at 23:34
  • Just to clarify: Do you want a general solution, that would always work to differentiate a call between template overloads with one vs two parameters or do you want only a solution specific to this case? – walnut Dec 30 '19 at 23:37
  • @walnut: General solution would be better; my specific scenario is mostly motivation for the problem. – einpoklum Dec 30 '19 at 23:40
  • @einpoklum Maybe this is by design i.e. the way you're trying to use C++ is not intended. Especially, using pointers to represent iterators sounds like a bad idea to me :). – Chenna V Dec 31 '19 at 00:32
  • @einpoklum due to some potential issue with my answer, I would recommend that you unaccept it for now so that I may delete it. At least until I am able to verify that it should actually work that way. It currently seems to me that the wording in [\[over.call.func\]/3](https://eel.is/c++draft/over.match.call#over.call.func-3) would actually require this to behave identically to a plain function call and I'm not yet sure which compiler is right… – Michael Kenzel Jan 08 '20 at 10:36
  • @MichaelKenzel: Ok, but I suggest you prepend a comment rather than delete it. – einpoklum Jan 08 '20 at 10:36
  • @einpoklum thx, I have added a comment for now. It will probably take a while until I can figure out what's going on here… – Michael Kenzel Jan 08 '20 at 10:42
  • @MichaelKenzel: But if the example works, how can it be wrong? – einpoklum Jan 08 '20 at 10:48
  • @einpoklum as pointed out by walnut, it doesn't work with msvc. And, based on my current understanding, there's a chance that msvc is actually right about that… – Michael Kenzel Jan 08 '20 at 11:13
  • [`std::type_identity_t`](https://en.cppreference.com/w/cpp/types/type_identity) perhaps? – Indiana Kernick Jan 20 '20 at 02:00

3 Answers3

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

Then call range1 instead of strf::range.

range1_ptr<T>(...) can always be used to explicitly call the template taking one template argument, but does not do any deduction from the arguments. range1 replicates the deduction from the original strf::range template.

This works, because [temp.deduct.funcaddr]/1 says that template argument deduction when taking the address of a function without target type of the conversion is done on each candidate function template as if the parameter and argument lists of a hypothetical call were empty. So the second template argument cannot be deduced for the second overload with two template parameters. The only candidate left is the first overload, which will be chosen as the target of the function pointer.

As long as there is no second candidate function template for which a valid template-id with only one argument can be formed, range1_ptr can always be used to call the function template taking one argument unambiguously. Otherwise, the instantiation of range1_ptr will give an error because of ambiguity.

walnut
  • 21,629
  • 4
  • 23
  • 59
  • Won't there be ambiguity about `strf::range`? – einpoklum Dec 30 '19 at 23:47
  • 1
    @einpoklum It compiles fine on GCC and Clang. I did not check the standard, but would be surprised if that is supposed to be ambiguous. – walnut Dec 30 '19 at 23:50
  • Maybe you should change the function name to `pretty_please_with_sugar_on_top()`? ... C++ can be so weird sometimes... – einpoklum Dec 30 '19 at 23:53
11

What about passing through a using ?

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

tfp x = &strf::range;

char const * a = "abcd";

(*x)(a, a+2);
max66
  • 65,235
  • 10
  • 71
  • 111
  • And this compiles? The second line looks particularly suspect. – einpoklum Dec 30 '19 at 23:46
  • @einpoklum - funny, isn't it? – max66 Dec 30 '19 at 23:47
  • @einpoklum - unfortunately isn't a general solution; works in this case because (if I'm not wrong) only the first `range()` version is compatible with `tpf`; other case can be different. – max66 Dec 30 '19 at 23:50
  • @einpoklum - in the second line you can also explicate the template parameter (`tfp x = &strf::range;`); this way I suppose you have a general solution, almost equivalent to the walnut's one – max66 Dec 30 '19 at 23:53
0

A solution is

1) first of all, you should specify the type for the second argument, e.g. (char *)(some_char_ptr + some_length)

2) don't use const for the both, this works well:

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

You can try to replace (char *) with (const char *) at left OR at right, it still works.

tontonCD
  • 320
  • 2
  • 6
  • 1. There _isn't_ a second argument. I want the single-argument template. 2. Interesting hack! -1 for the first suggestion and +1 for the second :-P – einpoklum Jan 09 '20 at 15:43