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 a struct {long long x;}
with overloaded typecast operator for both long long
and int
, the latter with narrow check. This is my best idea so far as it gives an ambiguous call
compile-time error when void 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:
#include <iostream>
// (A)
void bar(int y) { std::cout << "bar(int)\n"; }
// (B)
//void bar(long long) { std::cout << "bar(long long)\n"; }
// (C)
template <typename T>
void bar(T) = delete;
// (C.SP1)
template<>
void bar<long long>(long long y) { bar(narrow<int>(y)); }
int main() {
long long a = 12LL;
bar(a); // bar(int)
// bar(long long) if `bar(long long)` above is available.
}
If void bar(long long);
is not available, any call to bar(a)
for an argument a
of type long long
will favour the non-narrowing function template overload (C), whose primary template has been deleted to only allow invocation when T
is exactly long long
(no conversions) through the specialization (C.SP1). Once void 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 bar
overload (the function template above) may break overload resolution of bar
when compiling the library itself, you could add a public wrapper for bar
and place the overload resolution delegation above in the TU where the wrapper is defined, applying internal linkage for the added bar
overload by declaring it in an unnamed namespace. E.g.:
// foo.h
#pragma once
void foo(long long y);
// foo.cpp
#include "foo.h"
#include "gsl/gsl_narrow"
#include "the_lib/bar.h"
namespace {
template <typename T>
void bar(T) = delete;
template<>
void bar<long long>(long long y) { bar(narrow<int>(y)); }
} // namespace
void foo(long long y) {
bar(y);
}