tags:

views:

216

answers:

2

I'm writing some simple point code while trying out Visual Studio 10 (Beta 2), and I've hit this code where I would expect SFINAE to kick in, but it seems not to:

template<typename T>
struct point {
    T x, y;
    point(T x, T y) : x(x), y(y) {}
};

template<typename T, typename U>
struct op_div {
    typedef decltype(T() / U()) type;
};

template<typename T, typename U>
point<typename op_div<T, U>::type>
operator/(point<T> const& l, point<U> const& r) {
    return point<typename op_div<T, U>::type>(l.x / r.x, l.y / r.y);
}

template<typename T, typename U>
point<typename op_div<T, U>::type>
operator/(point<T> const& l, U const& r) {
    return point<typename op_div<T, U>::type>(l.x / r, l.y / r);
}

int main() {
    point<int>(0, 1) / point<float>(2, 3);
}

This gives error C2512: 'point<T>::point' : no appropriate default constructor available

Given that it is a beta, I did a quick sanity check with the online comeau compiler, and it agrees with an identical error, so it seems this behavior is correct, but I can't see why.

In this case some workarounds are to simply inline the decltype(T() / U()), to give the point class a default constructor, or to use decltype on the full result expression, but I got this error while trying to simplify an error I was getting with a version of op_div that did not require a default constructor*, so I would rather fix my understanding of C++ rather than to just do what works.

Thanks!


*: the original:

template<typename T, typename U>
struct op_div {
    static T t(); static U u();
    typedef decltype(t() / u()) type;
};

Which gives error C2784: 'point<op_div<T,U>::type> operator /(const point<T> &,const U &)' : could not deduce template argument for 'const point<T> &' from 'int', and also for the point<T> / point<U> overload.

A: 

I would say your error is in here:

template<typename T>
struct point {
    T x, y;
    point(T x, T y) : x(x), y(y) {}
};

Change your struct definition to this:

template<typename T>
struct point<T> {
    T x, y;
    point(T x, T y) : x(x), y(y) {}
};

If you want to use a generic type T, you need to specify it in the definition.

thorkia
thorkia, you don't need the T in the definition the way you mentioned. It would be needed only if you're doing a specialization, for example, like template <class T> struct point<T*> { /* ... */ };
ltcmelo
+2  A: 

Not 100% sure. It appears that the compiler needs to instantiate both overloads to determine which is better, but while trying to instantiate the other op_div with T = int and U = point<float>, this leads to an error that is not covered by SFINAE (the error is not that op_div doesn't have type in this case, but that type cannot be determined).

You could try to disable the second overload if the second type is a point (boost::disable_if).

Also, what seems to work is postponed return type declaration (doing away with the op_div struct, but depending on which C++0x features are supported by your compiler):

template<typename T, typename U>
auto
operator/(point<T> const& l, point<U> const& r) -> point<decltype(l.x / r.x)> {
    return {l.x / r.x, l.y / r.y};
}

template<typename T, typename U>
auto
operator/(point<T> const& l, U const& r) -> point<decltype(l.x / r)> {
    return {l.x / r, l.y / r};
}
UncleBens
Hah, you got the same idea. +1 xD
Johannes Schaub - litb
I've deleted my answer because i find your's is more to the point :) Have fun
Johannes Schaub - litb
The second decltype should be `point<decltype(l.x / r)>`, though :) Using C++0x's enable_if it can look like `enable_if<!is_point<U>::value, op_div<T, U>>::type::type` with is_point being `template<typename T> struct is_point : false_type {}; template<typename T> struct is_point<point<T>> : true_type {};`
Johannes Schaub - litb
Thanks, corrected the mistake.
UncleBens
Just to mention that enable_if is available as a boost library if necessary >> http://www.boost.org/doc/libs/1_35_0/libs/utility/enable_if.html
Matthieu M.
Thanks! I did `-> point<decltype(l.x / l.y)>` originally, but changed it for some reason :/. Also, Thanks @litb, as well! I had not used enable_if before, although I knew it existed. It seems to not fix this though. I'll accept this later today if nobody explains why this isn't covered by SFINAE (my reading of the draft seems to say any error: 14.9.3 in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2960.pdf)
Simon Buchan
I suspect it's my fault, because i deleted my answer. I will put it as a comment so you can read it: *SFINAE applies during argument deduction and substitution of explicit template arguments into function types or types in specialization arguments (like in `template<typename T> struct f<T, typename T::type> {};`). It does not apply when instantiating a template's body. So in your example if the template is selected and instantiated, and when an error occurs then, it's too late for an SFINAE error.* Hope this makes sense. Putting it in decltype in the function return type should work.
Johannes Schaub - litb
Notice that the draft says: *Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure. [ Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such side effects are not in the "immediate context" and can result in the program being ill-formed. — end note ]*
Johannes Schaub - litb
@litb: OK, that makes sense, but surely the error instantiating `op_div` with `point` happens during deduction, since it's in the return type?
Simon Buchan
Ahh, I think I get it now, it's because of *Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.* in 14.9.2.8 - So even though `op_div` is instantiated during argument deduction, it is not in the *immediate context* and ill-formedness is an error. Interesting that that paragraph is the only instance of *immediate context* :).
Simon Buchan
@litb: If you undelete your answer with the 14.9.2.8 text added, I'll accept it - I'd like to get this information out of the comments.
Simon Buchan