15

I ran into some trouble trying to implement a smart equality test macro-type template function in Visual C++ 2010 that had to do with a bug in VS in regard to default arguments of template functions. I fixed it by wrapping the value of the parameter in an extra function, but now I found that I can't use the function twice in one line!

Header file:

// example.h
#pragma once

#include <limits>

namespace myspace
{

// Need to define this separately to avoid a Visual Studio bug
template<typename T> T epsilon() { return std::numeric_limits<T>::epsilon(); }

// A generic equality test
template<typename T> inline bool smartEqual(
    const T &v1, 
    const T &v2, 
    const T &eps = epsilon<T>())
{
    return (v1 == v2);
}

// Template specialization for floating-point numbers
template<> bool smartEqual<float>(
    const float &v1, 
    const float &v2, 
    const float &eps);

} // namespace myspace

Source file:

// example.cpp
#include "example.h"

using namespace std;
using namespace myspace;

// equal-macro specialization for floats using epsilon
template<> bool myspace::smartEqual<float>(
    const float &v1, 
    const float &v2, 
    const float &eps)
{
    return (fabs(v1 - v2) < eps);
}

int _tmain(int argc, _TCHAR* argv[])
{
    float a,b;
    bool x = smartEqual(a,b); // works ok
    bool x = smartEqual(a,b) && smartEqual(b,a); // error
    return 0;
}

The error is reported as follows:

------ Build started: Project: test, Configuration: Debug Win32 ------
test.cpp
c:\users\ninja\documents\visual studio 2010\projects\test\test\test.cpp(24): error C2440: 'default argument' : cannot convert from 'const float *' to 'const float &'
Reason: cannot convert from 'const float *' to 'const float'
There is no context in which this conversion is possible

The offending line is the one where I try to call smartEqual() twice using the logical AND.

I don't understand why this happens. Changing "eps" from a reference type to a straightforward value type fixes it, but I wish I knew what was going on.

Thanks!

Community
  • 1
  • 1
neuviemeporte
  • 6,310
  • 10
  • 49
  • 78
  • If you instantiate `smartEqual<>()` 2 times with 2 _different_ types, won't it leave you with 2 versions of `epsilon()` which differ in return type only? – Pavel Zhuravlev Apr 27 '12 at 02:25
  • @Pavel: That is indeed also an interesting question, but as int ei = epsilon(); float ef = epsilon(); compiles OK, it seems they are somehow separate functions. I mean, the compiler somehow distinguishes between them. Not sure how though. – neuviemeporte Apr 27 '12 at 02:32

3 Answers3

13

I think you've now hit this VS10 bug.

Your code compiles OK on VS11 Beta.

You could possibly avoid the default value (which seems to be a major issue for VS10) by changing smartEqual to:

template<typename T> inline bool smartEqual(
    const T &v1, 
    const T &v2)
{
    return (v1 == v2);
}

and simply specialising for float (and double) like this:

template<> bool myspace::smartEqual<float>(
    const float &v1, 
    const float &v2)
{
    return (fabs(v1 - v2) < std::numeric_limits<float>::epsilon());
}


Another option is to change the epsilon parameter to pass by value:

template<typename T> inline bool smartEqual(
    const T &v1, 
    const T &v2, 
    T eps = epsilon<T>())
{
    return (v1 == v2);
}
Fraser
  • 74,704
  • 20
  • 238
  • 215
  • 1
    Hold me down, I'm on fire! I shudder to think what other bugs I might unravel before the day is done. ;) – neuviemeporte Apr 27 '12 at 02:06
  • @neuviemeporte MS should hire you to test VS before they release it. – Seth Carnegie Apr 27 '12 at 02:16
  • @neuviemeporte and don't forget about this little gem: `template class A { class B : public C; };` – Seth Carnegie Apr 27 '12 at 02:18
  • Seriously though, any idea on how to fix/work around this in a correct way if I'm unwilling to upgrade to a beta build of VS? – neuviemeporte Apr 27 '12 at 02:22
  • I'd avoid using the default value in `smartEqual`. – Fraser Apr 27 '12 at 02:22
  • @Fraser: That's really inconvenient, I'd hate to have to fill the value in everywhere where it's needed explicitly. Wouldn't making the value a non-reference type be better? The objects this operates on are usually small, primitive types: ints/floats/doubles. – neuviemeporte Apr 27 '12 at 02:28
  • @Fraser: The idea was to be able to use smartEqual with an other epsilon value than the machine defined one, if I needed more leniency for example. Hmmm. I guess I could create an overload of smartEqual(), one with eps, the other without... :-^ – neuviemeporte Apr 27 '12 at 02:38
  • @neuviemeporte You can set the default value of epsilon argument to 0 and check it in template code: something like `if(eps == 0) return(smartEqual(v1,v2,std::numeric_limits::epsilon());` You will loose the ability to call the function with zero epsilon argument, and its ugly, but... – Pavel Zhuravlev Apr 27 '12 at 02:39
  • @neuviemeporte Ahh - OK. Probably best to just change to pass-by-value for the third parameter then. I've updated the answer again :) – Fraser Apr 27 '12 at 02:49
2

code failed in VS2010 but OK in Intel compiler. looks like a bug in VS2010

RolandXu
  • 3,566
  • 2
  • 17
  • 23
2

After some consideration, I've decided to go with a still another solution that @Fraser suggested (although I got the inspiration from him) and write my own answer:

  1. The first solution robs me of the flexibility of being able to use a custom value of eps.
  2. The second solution with the pass-by-value feels wrong, especially if in the future I will decide to use this function for some more contrived types.

Since VS seems overriden with bugs in regard to default values of parameters (only in templates?), it seems the most sensible thing to do is sidestep the issue by creating two versions of smartEqual; with and without the eps (using the default), which pretty much does the same thing, if not as concisely:

// An equality test that doesn't require the value of eps, default will be used
template<typename T> inline bool smartEqual(
    const T &v1, 
    const T &v2)
{
    return (v1 == v2);
}

// Float specialization: return (fabs(v1 - v2) < std::numeric_limits<float>::epsilon());
template<> inline bool smartEqual<float>(
    const float &v1, 
    const float &v2);

// A custom-eps value equality test
template<typename T> inline bool smartEqual(
    const T &v1, 
    const T &v2, 
    const T &eps)
{
    return (v1 == v2);
}

// Float specialization: return (fabs(v1 - v2) < eps);
template<> bool smartEqual<float>(
    const float &v1, 
    const float &v2, 
    const float &eps);
neuviemeporte
  • 6,310
  • 10
  • 49
  • 78