Why doesn't my templated function call the correct overload?

Started by
7 comments, last by Servant of the Lord 10 years, 8 months ago

I have this function:


template<typename Type>
typename std::enable_if<!(std::is_arithmetic<Type>::value || std::is_enum<Type>::value), size_t>::type
Serialize(Serializer&, Type&, size_t)
{
	static_assert(false, "Serialize<TYPE>() hasn't been specialized for this type.");
	return size_t();
}

And this overload:


template<typename ElementType>
size_t Serialize(Serializer &serializer, std::vector<ElementType> &container, size_t pos)
{
	//...stuff...
}

I give it a std::vector<std::pair<std::string, int>> (for testing), and it chooses the first function.

Isn't C++ supposed to choose the more-specific function? I mean, this isn't a specialization, it's an overload. Aren't overloads supposed to be chosen over specializations or templates?

As a humorous aside, I had to walk through the debugger to find which templated function was getting called (out of about twenty), because in the main template declaration, I accidentally had static_assert(true, "") instead of static_assert(false, ""). rolleyes.gif

Advertisement

Is it really defined that the compiler can choose more specific, non-exact matching, overloads? I mean we know that the compiler will choose the exact match of an overload if there is one available but i don't know about non-exact matching, more specific overloads :p

Assuming that it does, it would probably complicate some things. If we have a simple template function for example like:


template< typename T >
T Sum( T &v1, T &v2 )
{
  return v1 + v2;
}

// Overload
double Sum( double &v1, double &v2 )
{
  return v1 + v2;
}

If we call Sum passing in two float values, sure it can use the double version by implicitly converting float to double but may cause a bit of a performance hit. That's just a very simple example but if we have a much much more complicated templated function like yours and the compiler would choose something non-exact, but might still work, it may choose the wrong overload with a behavior that we may not want.

I'm just putting out my thoughts. I'm not exactly sure if this makes sense it's actually been a while since i messed around with complex template stuff.

I don't understand the exact details, but you can find some of the information here.

I believe the compiler is allowed to make one implicit cast, and I think the double overload of your function would be called, because overloads are chosen over templated functions.

*goes to test*

Uh, no, the templated version was called. I guess the overload wasn't specific enough. wacko.png

Still, my overload is more specific than the base template, even if not exact. No conversion is needed either. At the very least, it should be ambiguous, shouldn't it? But it's not, it's choosing the wrong one.

Anyone have an idea on how I could make my templated vector overload work?

One problem is by using std::vector<T> as a parameter, prevents the overload in participating in template argument deduction. You need to remove std::. The compiler cannot do template argument deduction with namespace qualified types.

Another problem is that the type T& can be a better than vector<Type> & for matching const vectors, and rvalues. You say the type is "std::vector<std::pair<std::string, int>>" but it might not be in you actual code. Matching on T& is very greedy, so I would not recommend using it for overload checking like that.

For more help, you could post the actual code that instantiates the template.
//Convience function to read or write a file. Meant to be used with "Serialize< MyFileType >" overloads.
template<typename FileFormat>
bool SerializeToFile(FileFormat &fileStruct, const std::string &filepath)
{
	Serializer serializer;
	size_t size = Serialize(serializer, fileStruct);
	
	//...saves the serializer to file and returns true or false...
}

//Overloaded Serialize function for when 'pos' isn't specified.
//This is used for any template with the format: "Serialize(Serializer&, <TYPE>, size_t pos)"
template<typename Type>
size_t Serialize(Serializer &serializer, Type &type)
{
	return Serialize<Type>(serializer, type, serializer.Position);
}

//The basic declaration, for non-builtin types.
template<typename Type>
typename std::enable_if<!(std::is_arithmetic<Type>::value || std::is_enum<Type>::value), size_t>::type
Serialize(Serializer&, Type&, size_t)
{
	static_assert(false, "Serialize<TYPE>() hasn't been specialized for this type.");
	return size_t();
}

//The serializer for vectors that I want to get called, but that isn't getting called.
template<typename ElementType>
size_t Serialize(Serializer &serializer, std::vector<ElementType> &container, size_t pos)
{
	//Serialize the size of the container.
	uint16_t numElements = static_cast<uint16_t>(container.size());
	pos = Serialize<uint16_t>(serializer, numElements, pos);
	
	//Serialize each element in the container.
	container.resize(numElements);
	for(size_t i = 0; i < numElements; i++)
	{
		pos = Serialize(serializer, container[i], pos);
	}
	
	return pos;
}

//...other Serialize<> functions, including an overload for std::pair<>, and including an overload for std::string, and an overload that catches basic integer types.
How I'm calling it (copy+pasted - it's just a test usage).
std::vector<std::pair<std::string,int> > ouputVector = {{"Three",3},{"Five",5},{"Seven",7},{"ThreeFiveSeven",357}};
SerializeToFile(ouputVector, "P:/Images/Blah/SerializeTest.dat"); //Actual filepath. =P
I believe this is what you are looking for: on template functions, specialization, and overloading rules

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Problem solved! smile.png

I converted most my specialized functions to just overloads (which make more sense anyway, as the article ApochPiQ posted points out). I also commented out the 'catch all' template with the static_assert, at least for now.

However, my problematic function was already an overload anyway, so why wasn't it working?

The code I posted right before ApochPiQ's post has a very subtle template bug in it that I kept on missing.

In the second function posted looks like this:


template<typename Type>
size_t Serialize(Serializer &serializer, Type &type)
{
	return Serialize<Type>(serializer, type, serializer.Position);
}

The function I was hoping for it to call, looks like this:


template<typename ElementType>
size_t Serialize(Serializer &serializer, std::vector<ElementType> &container, size_t pos)
{
   //...stuff...
}

Very subtle, at least to me.

The first function's body calls a templated function, and demands the function takes a template argument of 'Type'.


return Serialize<Type>(serializer, type, serializer.Position);
                ^^^^^^

I was passing std::vector<blah> into the function, which means, that function is being called as:


Serialize< std::vector<blah> > (...)

Which tries to call the function I want:


template<typename ElementType>
size_t Serialize(Serializer &serializer, std::vector<ElementType> &container, size_t pos)

But tries it as:


template<>
size_t Serialize< std::vector<blah> >(Serializer &serializer, std::vector< std::vector<blah> > &container, size_t pos)
                                                              ^ 1st vector ^ 2nd vector

And that isn't a good fit, so SFINAE kicks in and it tries the other functions instead.

My function itself was fine, but the function that called it told it that 'std::vector<blah>' needed to be the first template argument, instead of letting the function try and figure it out on its own. mellow.png

Thank you for all the help, gentlemen! This bug was really throwing me for a loop.

[quote name="Servant of the Lord" post="5080903" timestamp="1374898418"
And that isn't a good fit, so SFINAE kicks in and it tries the other functions instead.[/quote]
I can see that leaving it to type deduction will correctly match the one you want but why isn't the double nested vector a good fit nonetheless? To me it doesn't look like a substitution failure, unless I'm just not seeing it?

Or is it simply that the other function is a more specific overload because it doesn't involve substituting into template args?

std::vector<int> can't be converted to std::vector<std::vector<int>>, at least in my test here.

But it does work, if I wrap the argument in brackets like: { singleVec } (because then I am explicitly initializing the double-vector, using the single vector as the first element of the double vector).

My code above in the earlier post doesn't have it wrapped in brackets, because I wasn't intending to do std::vector< std::vector< TYPE > >

That was an accidental result of my overly micro-managing of the function call. smile.png

[Edit - two hours layer, while refilling the ice-trays:] The reason why it won't auto-convert, is because std::vector doesn't have a constructor that takes a single element as a parameter.

You can't do:

int myInt = 357;
std::vector<int> myVec = myInt;

You have to do:

int myInt = 357;
std::vector<int> myVec(N, myInt); //Where 'N' is the number of elements you want to copy-construct using 'myInt'.

Or:

int myInt = 357;
std::vector<int> myVec = {myInt}; //Using initializer-lists to construct myVec.

The std::vector class doesn't have a single-element parameter'd constructor.

Howsoever, even if it did, that'd only work if my Serialize() function took the argument by value or by const-reference (or by r-value reference).

But my Serialize() function was taking the argument by non-const reference (because it reads or writes, depending on the nature of the 'Serializer' class passed in), so no conversion can take place, otherwise you'd be referencing a temporary, which isn't allowed with non-const references. (Example)

This topic is closed to new replies.

Advertisement