I thought that a non-template would always take precedence over a template, if the arguments match up just as well.
But:
template <typename... Args>
void Trace(Args&&... args)
{
throw "what the frak";
}
void Trace(const int&)
{}
int main()
{
Trace(42);
}
This throws unless I make the non-template Trace(int) or Trace(int&&), i.e. not taking a const ref.
It's kind of annoying because I want to provide a different implementation for specific argument types where the real implementation of the template does not make sense (and, in fact, would not compile).
I can fix it by making the second function a specialisation, but only if I match argument types by taking an rvalue reference:
template <>
void Trace(int&&)
{}
And, well, maybe I didn't want to do that! Maybe I want to call it with a const thing sometimes and would rather not specialise twice.
Is it correct and standard-abiding that simply providing a non-template overload works this way, in this example? It's the first time I've noticed this behaviour (not that that necessarily means anything!).
42 is an rvalue of type int, therefore it binds more closely to int&& rather than const int&. This is why it is calling your template Trace function.
If you were to call
const int i{};
Trace(i);
then your Trace(const int&) overload would be invoked.
Possible workarounds:
Add a Trace(int&&) overload that invokes Trace(const int&). You might also need a Trace(int&);
Use SFINAE on the templated Trace to prevent instantiation when the first argument is an int;
template <typename Arg, typename... Args>
auto Trace(Arg&& arg, Args&&... args)
-> std::enable_if_t<!std::is_same_v<std::decay_t<Arg>, int>>
{
throw "what the frak";
}
Change the templated Trace to take const Args&... instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With