IniReader template overrides become ambiguous
I wouldn't worry too much about temporaries (shouldn't they be optimized away?), but I think you're focusing on the smaller problem. You fundamentally have the following options:


Disambiguate by disallowing certain combinations, like you say you've currently done (readValue&lt;T&gt;(string, string), readValue&lt;T&gt;(string, string, T), and readValue&lt;T&gt;(string, string, int, T) but no readValue&lt;T&gt;(string, string, int))
Disambiguate by changing parameters to the function (this could be by reordering non-template parameters e.g. readValue&lt;T&gt;(string, string, T) vs readValue&lt;T&gt;(string, int, string, T), or by the readValue&lt;T&gt;(IniPath, T) approach you describe where you make it someone else's problem)
Disambiguate by changing function names, e.g. readValue&lt;T&gt;(string, string, T) vs. readIndexedValue&lt;T&gt;(string, string, int) (and of course readIndexedValue&lt;T&gt;(string, string, int, T))
Disambiguate by changing function names the other way, e.g. readValue&lt;T&gt;(string, string) and readValue&lt;T&gt;(string, string, int) vs readValueDefault&lt;T&gt;(string, string, T) and readValueDefault&lt;T&gt;(string, string, int, T).


My opinion is that the first is too likely to create a pit of failure. Just like when you tried to add a non-defaulted readValue&lt;T&gt;(string, string, int) without immediately realizing the problem of doing so, users of your functions are likely to make the same mistake on the consumption side (hey, I've got readValue(sFoo, sBar) but now I need sBar 1, 2, and 3...) and wonder why things are compiling but not working correctly.

The second is okay; making it someone else's problem is a really good approach for some scenarios. Merely reordering the parameters like I mentioned can result in funky APIs like the deprecated parts of CRegKey::QueryValue where the order of parameters results in wildly different overloads that make the API hard to remember. I don't know whether this one is that bad, but I'm still predisposed against it.

The third is my favorite. It's simple, clean, and makes it easier to figure out what this extra int parameter is. The fourth is about as good, depending on whether indexes or defaults are more "natural", and of course you can combine both bullet's approaches.

But that's not the end of the story.

The bigger problem is this: You're migrating from existing usage with working code. You have to consider what the damage is, how to detect it, and how to fix it. Ideal case is things magically work 100%. Next best, in my opinion, is each call site either works the same as it used to or results in a compile-time error. Worse is a series of call sites you have to update, will find a pattern to do so, but sometimes the pattern is wrong (a change to IniPath might result in this). Worst by a long shot is call sites that change their meaning without any warning (you might be stuck here anyway if your call sites are ambiguous enough).

So do you have any call sites that will change meaning, and if so how will you address this? That will help determine how you disambiguate your function.