Incorrect overload resolution for 2-argument funct

2019-02-16 16:29发布

问题:

Let's take the following example program:

#include <cmath>

namespace half_float
{
    template<typename T> struct half_expr {};

    struct half : half_expr<half>
    {
        operator float() const;
    };

    template<typename T> half sin(const half_expr<T>&);
    template<typename T> half atan2(const half_expr<T>&, const half_expr<T>&);
}

using namespace std;
using half_float::half;

int main()
{
    half a, b;
    half s = sin(a);
    half t = atan2(a, b);
}

In VS 2010 this compiles just fine (ignore the obvious linker errors for now). But in VS 2012 this gives me:

error C2440: 'conversion' : cannot convert from 'float' to 'half_float::half'

So it seems overload resolution doesn't pick the version from namespace half_float (which ADL should accomplish), but the one from std using the implicit conversion to float. But the strange thing is, that this only happens for the atan2 call and not the sin call.

In the larger project, where this error actually first occured to me it also occurs for other 2-argument functions (or rather those with 2 half arguments), like fmod, but not for any 1-argument function. Likewise in the larger project it also works fine for gcc 4.6/4.7 and clang 3.1 without error, though I didn't test this SSCCE version explicitly there.

So my question is, is this erroneous behaviour on VS 2012's side (given that it only happens for 2012 and only for the 2-argument function), or did I oversee some subtleties in the overload resolution rules (which can indeed get a bit tricky, I guess)?

EDIT: It also happens if I'm directly using namespace half_float or put the whole thing in global namespace directly. Likewise does it also happen if I'm not using namespace std, but this is rather the VS-implementation putting the math functions in global namespace.

EDIT: It happens both with the original VC 2012 compiler as well as the November 2012 CTP of it.

EDIT: Although I'm not completely sure it is really a violation of the standard in the strictest sense, I have filed a bug for it based on the findings in my answer, since it is at least inconsistent to the definition of the 1-argument functions and deserves further investigation by the VS-Team.

回答1:

I think I found the cause. The C++ standard says in section 26.8 [c.math], that for the mathematical functions of the C library,

there shall be additional overloads sufficient to ensure:

  1. If any argument corresponding to a double parameter has type long double, then all arguments corresponding to double parameters are effectively cast to long double.
  2. Otherwise, if any argument corresponding to a double parameter has type double or an integer type, then all arguments corresponding to double parameters are effectively cast to double.
  3. Otherwise, all arguments corresponding to double parameters are effectively cast to float.

Which can also be seen in the atan2 documentation.

Those overloads are provided by VS 2012 through the use of a general function template of the form:

template<typename T,typename U> common_float_type<T,U>::type atan2(T, U);

So we have a template function whose instantiation would involve an implicit conversion (from half& to const half_expr<half>&) and a template function which can be instantiated directly. Thus the latter is preferred. This doesn't happen for the 1-argument functions because for those there only has to be a general version for integral arguments, which is provided by VS 2012 for only those using a std::enable_if of std::is_integral.

But I think the standard is a bit unclear about the fact that those "additional overloads" are to be provided only for builtin types. So in the end I'm still not sure if VS 2012 strictly violates the standard with its overly generic functions or if it is a viable implementation option to provide those.

EDIT: As it seems, there is already defect report 2086 for the standard's unclear wording and a fix is on it's way, limiting the requirement for those additional overloads to only arithmetic types. Since this seems to have always been the original intent (and realized by nearly all existing implementations) and it was merely the wording that was unclear, I would indeed regard this a bug in VS 2012's implementation.



回答2:

I just tried your code, and I figured out what was wrong with it.

Since you haven't implemented half::sin and half::atan2, the linker will throw an error anyway. So if you implement the methods half::sin and half::atan2, that should solve it (I implemented them by letting them return an empty half, which is, of course, meaningless).

After I took that step (providing a (meaningless) implementation of the two required methods), the error messages almost magically disappeared.

Maybe this isn't the solution to your problem, as I'm using GCC, and not VS.


EDIT: I just tried the sample I used with G++ with visual studio, which gave me a peculier error message. Provided the strangeness of the error, and the code working with GCC, I must conclude that this is a bug in VC2012.



回答3:

A workaround is to specialise _Common_float_type for half and half_expr to be an undefined type, so that SFINAE gets rid of the VS2012 version of atan2.

namespace std {
    template<class T1, class T2>
    struct _Common_float_type<half_float::half_expr<T1>, half_float::half_expr<T2>>;
    template<class T2>
    struct _Common_float_type<half_float::half, half_float::half_expr<T2>>;
    template<class T1>
    struct _Common_float_type<half_float::half_expr<T1>, half_float::half>;
    template<>
    struct _Common_float_type<half_float::half, half_float::half>;
}

Note that you have to specialise for all four combinations of half and half_expr, because template specialisation doesn't consider base classes.