#include <type_traits>
template <typename T>
struct Point2 {
Point2() = default;
Point2(const T& x, const T& y) : x(x), y(y) {}
template <typename U>
Point2(const U& other) : x(other.x), y(other.y) {}
template<typename U>
friend Point2<decltype(T() * U())> operator*(const Point2<T>& vec, U scale) {
return {vec.x * scale, vec.y * scale};
}
template<typename U>
friend Point2<decltype(T() * U())> operator*(U scale, const Point2<T>& vec) {
return {vec.x * scale, vec.y * scale};
}
T x;
T y;
};
int main() {
Point2<double> a;
double b;
auto c = a * b;
}
I'm trying to compile the above code with clang-16
and option -std=c++17
.
However, there is a compilation error:
ce.cpp:4:8: warning: stack nearly exhausted; compilation time may suffer, and crashes due to stack overflow are likely [-Wstack-exhausted]
struct Point2 {
^
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
friend Point2<decltype(T() * U())> operator*(const Point2<T>& vec, U scale) {
^
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:12:30: note: (skipping 472 contexts in backtrace; use -ftemplate-backtrace-limit=0 to see all)
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:17:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
friend Point2<decltype(T() * U())> operator*(U scale, const Point2<T>& vec) {
^
ce.cpp:28:14: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
auto c = a * b;
^
ce.cpp:12:26: fatal error: recursive template instantiation exceeded maximum depth of 1024
friend Point2<decltype(T() * U())> operator*(const Point2<T>& vec, U scale) {
^
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
friend Point2<decltype(T() * U())> operator*(const Point2<T>& vec, U scale) {
^
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:12:30: note: (skipping 1015 contexts in backtrace; use -ftemplate-backtrace-limit=0 to see all)
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:12:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
ce.cpp:17:30: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
friend Point2<decltype(T() * U())> operator*(U scale, const Point2<T>& vec) {
^
ce.cpp:28:14: note: while substituting deduced template arguments into function template 'operator*' [with U = Point2<double>]
auto c = a * b;
^
1 warning and 1 error generated.
Why does the compiler deduce the types with: U = Point2<double>
?
It's very obvious the type U
should be double
, right?
Actually, I can find a lot of ways to correct this compilation error.
Solution 1: Comment the template constructor
...
// template <typename U>
// Point2(const U& other) : x(other.x), y(other.y) {}
...
Solution 2: Directly using auto
instead of decltype
to deduce the return type for either
operator*
...
template<typename U>
friend auto operator*(const Point2<T>& vec, U scale) {
return Point2{vec.x * scale, vec.y * scale};
}
...
or
...
template<typename U>
friend auto operator*(U scale, const Point2<T>& vec) {
return Point2{vec.x * scale, vec.y * scale};
}
...
I can roughly think of a very suspicious aspect is the decltype(T() * U())
, but I can not explain it very clearly.
Can anybody figure it out?
Alao, let me repeat the most confusing question for me:
Why does the compiler deduce the types with: U = Point2<double> ?
Here's what happens in overload resolution for the expression a * b
:
Find the candidate functions and function templates for the function operator*
, when the arguments are an lvalue of type Point2<double>
and an lvalue of type double
. No built-in meanings of operator*
are viable. Since one argument has class type Point2<double>
, its two friend templates are candidates:
template<typename U>
Point2<decltype(double() * U())>
operator*(const Point2<double>&, U); // (A)
template<typename U>
Point2<decltype(double() * U())>
operator*(U, const Point2<double>&); // (B)
Attempt template argument deduction for each candidate template. Template (A) finds U = double
. Template (B) finds U = Point2<double>
.
Substitute the template arguments to determine the function types.
Point2<decltype(double() * double())>
operator*(const Point2<double>&, double); // (A1)
Point2<decltype(double() * Point2<double>())>
operator*(Point2<double>, const Point2<double>&); // (B1)
Both are okay so far, since (B1) can be called by implicit conversion from double
to Point2<double>
via the template <typename U> Point2(const U&);
constructor.
But that hasn't entirely determined the function types. The return types are part of the function type. For (A1), no problem, decltype(double() * double())
is double
, so the return type is Point2<double>
. For (B1), determining the type decltype(double() * Point2<double>())
requires overload resolution.
Find the candidate functions and function templates for operator*
. This time the arguments are a prvalue of type double
and a prvalue of type Point2<double>
. The same two function templates (A) and (B) are candidates.
Attempt template argument deduction for each candidate template. Template (A) finds U = Point2<double>
. Template (B) finds U = double
.
Substitute the template arguments to determine the function types.
Point2<decltype(double() * Point2<double>())>
operator*(const Point2<double>&, Point2<double>); // (A2)
Point2<decltype(double() * double()>
operator*(double, const Point2<double>&); // (B2)
Like (B1) above, (A2) is okay so far because of the implicit conversion. The return type of (B2) is double
. Determining the return type of (A2) requires overload resolution.
Find the candidate functions and function templates for operator*
. This time the arguments are a prvalue of type double
and a prvalue of type Point2<double>
....
Uh oh. Steps 4 and 7 are both trying to figure out decltype(double() * Point2<double>())
, which would be the return type of both (A1) and (B2). This process will never end, since that type ends up depending on itself.
Some ways to improve the situation without losing the functionality you apparently want:
template<typename U> Point2(const U&);
as explicit
. It's generally a good idea to apply explicit
to any constructor other than copy/move which can be called with one argument (including situations where all parameters after the first have default arguments, and variadic templates).Point2<T>
can be initialized with any type U
at all? Or just other Point2<U>
specializations? Or at least, U
needs to be a class which has accessible members x
and y
...operator*
functions could check that the parameter intended to be a scalar is not a Point2
specialization.