Template madness - template operator not found

Started by
10 comments, last by Zlodo 8 years, 1 month ago

I am improving my template math library and I came upon the following problem:


typedef std:size_t Size;

template < typename T >
class A
{
	public:
	
		template < Size x >
		class B
		{
			T asdf[x];
		};
};

template < typename T, Size x >
inline A<T> operator + ( T value, const typename A<T>::template B<x>& vec )
{
	return A<T>();
}

int main( int argc, char** argv )
{
	A<Float>::B<2> b;
	5.0f + b; // ERROR: match for operator not found
}

For some reason the + operator is not found by the compiler. If i remove the first template argument from the operator (typename T) and explicitly use a type like float instead, the operator is found just fine.

Any idea how to work around this limitation? Note: B must be within the scope of A because both classes need the concrete type of the other.

Advertisement

Template madness indeed.

Why are you doing this? The few times I've seen people try to pull out fancy template-based math libraries they fail so badly in both performance and compilation times that they are quickly left by the roadside. What is it you are hoping to accomplish here?

With GCC 5.1, it's saying that it can't deduce the second parameter ("Size x") of the template.

Explicitly stating it works, however:


int main( int argc, char** argv )
{
	A<float>::B<2> b;
	//5.0f + b; // ERROR: match for operator not found
	
	operator+<float,2>(5.0f, b); //Compiles
}

With VS2015, even operator+<float>(5.0f, b); works.

I have no idea why the unspecified version doesn't, since it seems like it ought to, but trying to reason about template deduction is best left to those on the fringe of sanity.

I was trying to add swizzling to my vector classes and came upon this problem when implementing a full set of arithmetic operators for the swizzled vectors.

It's not a huge issue really, it only affects the operators where the first operand is a primitive type, the other operators can be class members and work fine.

There is a very detailed, specific, complicated set of patterns that a C++ compiler is supposed to be able deduce as function template arguments.

One part of why this doesn't work is very simple: each parameter is deduced independently, so the float for the first argument doesn't help at all for the second one.

For the other part, I am not 100% sure, but generally there are strict restrictions regarding non-type template parameters and qualified name lookups, and you have both there. There is a simple example in the standard stating an argument type of the form A<T1>::B<T2> will not be deduced.

You might get closer to an acceptable pattern by taking B out of A.

This works in VS2015:


# include <cstdint>
# include <iostream>
# include <vector>
# include <type_traits>
# include <string>


using namespace std;

template<typename T> class A {
	public:

		// internal B
		template<size_t x> class B {
			T asdf[x];
			};

		template<typename T> struct IsTypeB { 
			static const bool value = false;
			static const size_t size = 0;
			};

		template<size_t x> struct IsTypeB< B<x> > { 
			static const bool value = true;
			static const size_t size = x;
			};

		// internal C
		template<size_t x> class C {
			T asdf[x];
			};

		template<typename T> struct IsTypeC { 
			static const bool value = false;
			static const size_t size = 0;
			};

		template<size_t x> struct IsTypeC< C<x> > { 
			static const bool value = true;
			static const size_t size = x;
			};
	};

//template<typename T, Size x> inline A<T> operator+(T value, const typename A<T>::template B<x>& vec ) {
//	return A<T>();
//	}

template<typename T0, typename T1> 
	auto operator+(T0 v, const T1&  x) -> typename std::enable_if< A<T0>::IsTypeB<T1>::value , A<T0> >::type {
	cout << "size of B<x> = " << A<T0>::IsTypeB<T1>::size << endl;
	return A<T0>();
	}

template<typename T0, typename T1> 
	auto operator+(T0 v, const T1&  x) -> typename std::enable_if< A<T0>::IsTypeC<T1>::value , A<T0> >::type {
	cout << "size of C<x> = " << A<T0>::IsTypeC<T1>::size << endl;
	return A<T0>();
	}

	
// ----- main -----
void main() {

	A<float>::B<2> b;
	A<float>::C<3> c;

	5.0f + b;
	6.0f + c;

	cout << "done" << endl;

	getchar();
	}

As Pink Horror pointed out you're not gonna be able to deduce A<T1>::B<T2> directly. But you can 'hoist' it into the main class then use SFINAE to filter functions as necessary.

You need


template<class T>
template<Size x>
inline A<T> operator + ( T value, const typename A<T>::template B<x>& vec )
{
return A<T>();
}

^ won't work since the operator is at namespace scope, gives a "too many template argument lists" error.

I managed to solve my issue by figuring out a way to put the class "B" at namespace scope (with a forward declaration). Now the operators are found as expected.

Well then define it as a friend inside the class (will make it global). or Define it in the global namespace.

This topic is closed to new replies.

Advertisement