Suppose I had the following code where bar is defined in another library:
void bar(int x); /* this declaration comes from some included header file */
void foo(long long y)
{
return bar(y);
}
To make failures detectable, the GSL instructs me to use gsl::narrow as follows:
void bar(int x); /* this declaration comes from some included header file */
void foo(long long y)
{
return bar(narrow<int>(y));
}
My problem is that, suppose I knew void bar(long long x); is likely to appear in future releases of the library, the first version will automatically switch to use it (by deduction rules), whereas the second version will not. Is there any way to detect narrowing failures while void bar(long long x); is not available, and switch to just calling it when released?
I tried:
bar(narrow(y));, but the template argument of narrow could not be deduced.bar(superint{y}), where superint is astruct {long long x;}with overloaded typecast operator for bothlong longandint, the latter with narrow check. This is my best idea so far as it gives anambiguous callcompile-time error whenvoid bar(long long x);is added, making us aware of the places to update our codebase when time is right. It doesn't look like something you would put in the GSL though, so I am not quite satified..
UPDATE
There are plenty of good answers, but they all have to be implemented per-function rather than per-argument. Ideally the solution should look something like the GSL where you just do something to the arguments that are in risk of narrowing. The visitor pattern could be useful here, where we would just have to rewrite quz(xi,yi,z,w) to superint_visit(quz, superint{xi},superint{xi},z,w); assuming xi and yi were the integer arguments in risk of narrowing. Something like:
struct superint
{
long long x;
operator long long() { return x; }
operator int() { return narrow<int>(x); }
};
template <typename F, typename... Args>
auto superint_visit(F func, Args&&... args)
{
/* if replacing 'superint' with 'long long' in Args
gives a function that is declared, call that function
(using static_cast<long long> on all 'superint' args
to resolve ambiguity). Otherwise, use static_cast<int>
on all 'superint' args and call that function instead. */
}
All the above could live in gsl namespace, and I would just have to write my function as:
void foo(long long x)
{
return superint_visit(bar, superint{x});
}
Even though I accepted an answer here, I'll still like to hear from anyone who can make the above happen!
You can leverage the fact that overload resolution favours a perfect argument type match for a non-template function over a function template:
If
void bar(long long);is not available, any call tobar(a)for an argumentaof typelong longwill favour the non-narrowing function template overload (C), whose primary template has been deleted to only allow invocation whenTis exactlylong long(no conversions) through the specialization (C.SP1). Oncevoid bar(long long);at (B) becomes available, it will be chosen as a better viable candidate by overload resolution than that of the function template candidate.If you are worried that introducing an additional
baroverload (the function template above) may break overload resolution ofbarwhen compiling the library itself, you could add a public wrapper forbarand place the overload resolution delegation above in the TU where the wrapper is defined, applying internal linkage for the addedbaroverload by declaring it in an unnamed namespace. E.g.: