Use different sets of functions based on template parameters (C++ traits?)
문제
I have defined a class in C++ which holds an array of scalars of type T
for which I want to define operators like sin, cos, etc. For defining the meaning of sin
applied on an object of this class I need to know the meaning of sin
applied on the single scalar type T
. This means I need to use appropriate math libraries (corresponding to the scalar type T
) within the class. Here's the code as it is now:
template<class T>
class MyType<T>
{
private:
std::vector<T> list;
// ...
template<class U> friend const UTP<U> sin(const UTP<U>& a);
template<class U> friend const UTP<U> cos(const UTP<U>& a);
template<class U> friend const UTP<U> tan(const UTP<U>& a);
//...
};
template<class T> const UTP<T> sin(const UTP<T>& a)
{
// use the sin(..) appropriate for type T here
// if T were double I want to use double std::sin(double)
// if T were BigNum I want to use BigNum somelib::bigtype::sin(BigNum)
}
Currently, I have code that exposes the appropriate math library (using namespace std;) and then use ::sin(a)
inside the sin function for my class MyType
. While this works, it seems like a major hack.
I see that C++ traits can be used to store instance specific information (like which set of math functions to use when T
is double
, when T
is BigNum
, etc..)
I want to do something like this: (I know this doesn't compile but I hope this conveys what I want to do)
template<T>
struct MyType_traits {
};
template<>
struct MyType_traits<double> {
namespace math = std;
};
template<>
struct MyType_traits<BigNum> {
namespace math = somelib::bigtype;
};
and then in redefine my MyType class as:
template<T, traits = MyType_traits<T> >
class MyType
{
// ...
}
and then use traits::math::sin
in my friend function. Is there a way in which I can obtain the correct namespace (parameterized by T
) containing the math functions?
해결책
Isn't argument-dependent look-up good enough?
#include <cmath>
#include <iostream>
namespace xxx {
class X
{
};
X sin(X) { return X(); }
} //xxx
std::ostream& operator<< (std::ostream& os, xxx::X)
{
return os << "X";
}
template <class T>
void use_sin(T t)
{
using std::sin; //primitive types are not in a namespace,
//and with some implementation sin(double) etc might not be available
//in global namespace
std::cout << sin(t) << '\n';
}
int main()
{
use_sin(1.0);
use_sin(xxx::X());
}
This would work for X, because sin(X)
is defined in the same namespace as X. If you expect that not to be so, this probably won't help...
다른 팁
It's not the specific answer you're looking for, but wouldn't using template specialization be a simpler option?
As in...
template <typename T> T sin(T& t)
{
// does nothing
}
template <> float sin(float& t)
{
...
}
template <> double sin(double& t)
{
...
}
And so on?
I'm including this answer because I finally managed to get what I want (with help from very nice folk on ##c++ at irc.freenode.net). This method enables both ADL as well as a static set of places (xxx::math) to look in for the definition of the math functions.
This way, if the type parameter T of the class Test is such that:
- if T defines math functions as members then we can use ADL and not touch (add to) the namespace xxx::math.
- if T doesn't define math functions as members but uses functions from a specific namespace then we can add to the namespace xxx::math like in the example below.
Library looks like this:
#include <vector>
#include <cmath>
namespace xxx {
// include the usual math
namespace math {
using std::asin;
}
template <class T>
class Test
{
std::vector<T> array;
public:
Test(const typename std::vector<T>::size_type length)
{
assert(length >= 1);
array.assign(length, T(0.0));
}
friend std::ostream& operator<<(std::ostream& out, const Test<T>& a)
{
out << "(";
std::copy(a.array.begin(), a.array.end(), std::ostream_iterator<T>(out, ", "));
out << "\b\b)";
return out;
}
template<class U> friend const Test<U> asin(const Test<U>& a);
};
template<class U> const Test<U> asin(const Test<U>& a)
{
using math::asin;
Test<U> ret(a.array.size());
for (typename std::vector<U>::size_type i = 0; i < a.array.size(); ++i)
ret.array[i] = asin(a.array[i]);
// note how we use have a using math::asin; and then a call to asin(..) here,
// instead of a math::asin(..). This allows for ADL.
return ret;
}
} // xxx
Client looks like this:
#include <iostream>
#include <boost/math/complex.hpp>
// client, with some foresight, includes complex math
namespace xxx { namespace math {
using boost::math::asin;
} }
#include "test.h"
// demo
int main(int argc, char **argv)
{
using std::cout; using std::endl;
xxx::Test<double> atest(3);
cout << "atest: " << atest << endl;
cout << "asin(atest): " << asin(atest) << endl;
cout << endl;
xxx::Test<std::complex<double> > btest(3);
cout << "btest: " << btest << endl;
cout << "asin(btest): " << asin(btest) << endl;
cout << endl;
return 0;
}