This is further developing on what I was trying at this recent question of mine.
I want to create a bool(double, double) C-style function pointer to a functor with compatible signature. (The reason for the further attempt is that the earlier approach seemed to have restrictions on what can be constexpr-ed.)
How can I do in C++ the equivalent of the following working Python code:
#! /usr/bin/env python3
class FixPrecCompare:
'''Compares two floats to the given precision'''
def __init__(self, numDigits: int) -> None:
self.factor = 10 ** numDigits
def __call__(self, a: float, b: float) -> bool:
return int(a * self.factor) == int(b * self.factor)
class AreClose:
'''Same as Python's inbuilt math.isclose [which should be called areclose :-)]'''
def __init__(self, absTol = 0, relTol = 1e-09) -> None:
self.absTol = absTol; self.relTol = relTol
def __call__(self, a: float, b: float) -> bool:
diff = abs(a - b)
return diff <= self.absTol or \
diff <= abs(self.relTol * a) or \
diff <= abs(self.relTol * b)
# Now for signature compatibility (which is not a matter in Python)
# I make a function which internally uses the functor
def makeApproxCompare(FunctorType: type, *args):
functor = FunctorType(*args)
def fn(a: float, b: float) -> bool:
return functor(a, b)
return fn
approxCompare2 = makeApproxCompare(FixPrecCompare, 2)
approxCompare3 = makeApproxCompare(FixPrecCompare, 3)
areClose = makeApproxCompare(AreClose, 1e-3)
a, b = 4.561, 4.569
print(f"Compare {a:.3f}, {b:.3f} to 2 decimals: {approxCompare2(a, b)}")
print(f"Compare {a:.3f}, {b:.3f} to 3 decimals: {approxCompare3(a, b)}")
a, b = 2.7099, 2.7101
print(f"Compare {a} to {b} to 3 decimals: {approxCompare3(a, b)}")
print(f"Compare {a} to {b} using areClose: {areClose(a, b)}")
My attempt:
typedef bool(*SuccessTester)(double, double);
#include <cmath>
#include <iostream>
using namespace std;
struct FixPrecCompare
{
FixPrecCompare(int numDigits): factor(pow(10, numDigits)) {}
bool operator()(double a, double b) { return int(a * factor) == int(b * factor); }
private:
double factor;
};
struct AreClose
{
AreClose(double absTol, double relTol): abs_tol{absTol}, rel_tol{relTol} {}
bool operator()(double a, double b)
{
double diff = fabs(a - b);
return diff <= abs_tol ||
diff <= fabs(rel_tol * a) ||
diff <= fabs(rel_tol * b);
}
private:
double abs_tol, rel_tol;
};
template<typename Functor>
struct Helper
{
static Functor fr;
static bool func(double a, double b) { return fr(a, b); }
};
template<typename Functor, typename ... ArgTypes>
SuccessTester makeSuccessTester(ArgTypes ... args)
{
Helper<Functor> h;
h.fr = Functor{args ...};
return &h.func;
}
int main()
{
SuccessTester fp;
cout << boolalpha;
double a, b;
a = 4.561; b = 4.569;
fp = makeSuccessTester<FixPrecCompare>(2);
cout << "Compare " << a << ", " << b << " to 2 decimals: " << fp(a, b) << endl;
fp = makeSuccessTester<FixPrecCompare>(3);
cout << "Compare " << a << ", " << b << " to 3 decimals: " << fp(a, b) << endl;
a = 2.7099; b = 2.7101;
cout << "Compare " << a << ", " << b << " to 3 decimals: " << fp(a, b) << endl;
fp = makeSuccessTester<AreClose>(1e-3, 1e-10);
cout << "Compare " << a << ", " << b << " using areClose: " << fp(a, b) << endl;
}
But on GCC this gives:
/usr/bin/ld: /tmp/ccEkJxbk.o: warning: relocation against `_ZN6HelperI14FixPrecCompareE2frE' in read-only section `.text._ZN6HelperI14FixPrecCompareE4funcEdd[_ZN6HelperI14FixPrecCompareE4funcEdd]'
/usr/bin/ld: /tmp/ccEkJxbk.o: in function `bool (*makeSuccessTester<FixPrecCompare, int>(int))(double, double)':
<src>:(.text._Z17makeSuccessTesterI14FixPrecCompareJiEEPFbddEDpT0_[_Z17makeSuccessTesterI14FixPrecCompareJiEEPFbddEDpT0_]+0x38): undefined reference to `Helper<FixPrecCompare>::fr'
/usr/bin/ld: /tmp/ccEkJxbk.o: in function `bool (*makeSuccessTester<AreClose, double, double>(double, double))(double, double)':
<src>:(.text._Z17makeSuccessTesterI8AreCloseJddEEPFbddEDpT0_[_Z17makeSuccessTesterI8AreCloseJddEEPFbddEDpT0_]+0x4e): undefined reference to `Helper<AreClose>::fr'
/usr/bin/ld: <src>:(.text._Z17makeSuccessTesterI8AreCloseJddEEPFbddEDpT0_[_Z17makeSuccessTesterI8AreCloseJddEEPFbddEDpT0_]+0x55): undefined reference to `Helper<AreClose>::fr'
/usr/bin/ld: /tmp/ccEkJxbk.o: in function `Helper<FixPrecCompare>::func(double, double)':
<src>:(.text._ZN6HelperI14FixPrecCompareE4funcEdd[_ZN6HelperI14FixPrecCompareE4funcEdd]+0x2b): undefined reference to `Helper<FixPrecCompare>::fr'
/usr/bin/ld: /tmp/ccEkJxbk.o: in function `Helper<AreClose>::func(double, double)':
<src>:(.text._ZN6HelperI8AreCloseE4funcEdd[_ZN6HelperI8AreCloseE4funcEdd]+0x2b): undefined reference to `Helper<AreClose>::fr'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
And Clang is somewhat better:
<src>:40:7: warning: instantiation of variable 'Helper<FixPrecCompare>::fr' required here, but no definition is available [-Wundefined-var-template]
h.fr = Functor{args ...};
^
<src>:52:10: note: in instantiation of function template specialization 'makeSuccessTester<FixPrecCompare, int>' requested here
fp = makeSuccessTester<FixPrecCompare>(2);
^
<src>:32:20: note: forward declaration of template entity is here
static Functor fr;
^
<src>:40:7: note: add an explicit instantiation declaration to suppress this warning if 'Helper<FixPrecCompare>::fr' is explicitly instantiated in another translation unit
h.fr = Functor{args ...};
^
<src>:40:7: warning: instantiation of variable 'Helper<AreClose>::fr' required here, but no definition is available [-Wundefined-var-template]
h.fr = Functor{args ...};
^
<src>:59:10: note: in instantiation of function template specialization 'makeSuccessTester<AreClose, double, double>' requested here
fp = makeSuccessTester<AreClose>(1e-3, 1e-10);
^
<src>:32:20: note: forward declaration of template entity is here
static Functor fr;
^
<src>:40:7: note: add an explicit instantiation declaration to suppress this warning if 'Helper<AreClose>::fr' is explicitly instantiated in another translation unit
h.fr = Functor{args ...};
^
2 warnings generated.
/usr/bin/ld: /tmp/<src>-ce7af1.o: in function `bool (*makeSuccessTester<FixPrecCompare, int>(int))(double, double)':
<src>:(.text._Z17makeSuccessTesterI14FixPrecCompareJiEEPFbddEDpT0_[_Z17makeSuccessTesterI14FixPrecCompareJiEEPFbddEDpT0_]+0x1a): undefined reference to `Helper<FixPrecCompare>::fr'
/usr/bin/ld: /tmp/<src>-ce7af1.o: in function `bool (*makeSuccessTester<AreClose, double, double>(double, double))(double, double)':
<src>:(.text._Z17makeSuccessTesterI8AreCloseJddEEPFbddEDpT0_[_Z17makeSuccessTesterI8AreCloseJddEEPFbddEDpT0_]+0x28): undefined reference to `Helper<AreClose>::fr'
/usr/bin/ld: /tmp/<src>-ce7af1.o: in function `Helper<FixPrecCompare>::func(double, double)':
<src>:(.text._ZN6HelperI14FixPrecCompareE4funcEdd[_ZN6HelperI14FixPrecCompareE4funcEdd]+0x1f): undefined reference to `Helper<FixPrecCompare>::fr'
/usr/bin/ld: /tmp/<src>-ce7af1.o: in function `Helper<AreClose>::func(double, double)':
<src>:(.text._ZN6HelperI8AreCloseE4funcEdd[_ZN6HelperI8AreCloseE4funcEdd]+0x1f): undefined reference to `Helper<AreClose>::fr'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
But I'm not sure how to fix the code to make it work as desired. Please help! Thanks!
The principal problem with your approach is that you are trying to assign several different values to the same static member at once.
Helper<FixPrecCompare>::fris a unique variable, because you declared it static, and it can't hold bothFixPrecCompare(2)andFixPrecCompare(3)at the same time. So even if you make this compile, it will work as a global variable containing the last value you assigned to it, which is not a good thing to have anyways. I see 2 solutions here: either make everything a template parameter or use type erasure provided bystd::function.doubletemplate parameters here: