C++ template type deduction problem
motivation: I would like to create a utility class so that instead of having to write:
if( someVal == val1 || someVal == val2 || someVal == val3 )
I could instead write:
if( is(someVal).in(val1, val2, val3) )
which is much closer to the mathematical 'a is an element of (b,c,d)' and also would save on a lot of typing when the variable name 'someVal' is long.
Here is the code I have so far (for 2 and 3 values):
template<class T>
class is {
private:
T t_;
public:
is(T t) : t_(t) { }
bool in(const T& v1, const T& v2) {
return t_ == v1 || t_ == v2;
}
bool in(const T& v1, const T& v2, const T& v3) {
return 开发者_JAVA百科t_ == v1 || t_ == v2 || t_ == v3;
}
};
However it fails to compile if I write:
is(1).in(3,4,5);
instead I have to write
is<int>(1).in(3,4,5);
Which isn't too bad, but it would be better if somehow the compiler could figure out that the type is int
with out me having to explicitly specify it.
If you want to keep this syntax, you can use a helper function like :
template<class T>
class is_op {
private:
T t_;
public:
is_op(T t) : t_(t) { }
bool in(const T& v1, const T& v2) {
return t_ == v1 || t_ == v2;
}
bool in(const T& v1, const T& v2, const T& v3) {
return t_ == v1 || t_ == v2 || t_ == v3;
}
};
template< class U >
inline is_op<U> is( U const& v )
{
return is_op<U>( v );
}
int main(int argc, char* argv[])
{
is( 1 ).in( 1 , 2 , 4 );
}
The problem is quite amusing, it's true that boolean conditions can get hairy.
I myself tend to prefer writing special functions though, because the meaning here is hard to convey. What does:
if (someVal == val1 || someVal == val2 || someVal == val3)
means ?
Is
if ( is(someval).in(val1, val2, val3) )
// or
if ( is(someval).in(val1)(val2)(val3) ) // implements short-circuiting
// and removes arity issue
// using a proxy object
better?
I think it would be easier to read with:
bool isToBeLogged(const Foo& foo)
{
// Either
static std::set<Foo> ValuesToLog = /* some boost assign magic or whatever */;
return ValuesToLog.find(foo) != ValuesToLog.end();
// Or
return foo == val1 || foo == val2 || foo == val3;
}
if (isToBeLogged(someVal))
I guess it's a matter of style.
The advantages of the second method including:
- In case the test is done more than once, the logic is not scattered all around (so changes are easy)
- No need to comment the test, the method name does it already
Inconvients ? I guess it's more typing... oh well :p
template<typename T>
is<T> is_value(T value)
{
return is<T>(value);
}
int main()
{
bool r ;
r = is_value(1).in(3,4,5);
r = is_value(3).in(3,4,5);
return 0;
}
Regarding the general problem, a utility function like contains might be handy:
#include <boost/range.hpp>
template <class Range, class T>
bool contains(const Range& range, const T& value)
{
return std::find(boost::begin(range), boost::end(range), value) != boost::end(range);
}
(Boost usage makes it also accept arrays, although one might write that overload separately. This could also be overloaded for containers with the find member function.)
In C++0x this could be extended to support std::initializer_list<T>
* allowing rather nice usage:
if (contains({1, 2, 3}, value) {...}
* Not sure if it shouldn't work already, but my compiler requires an overload to make it work.
You are stuck with it - the constructor requires the type to be provided as a template parameter. And I should observe that I really don't like your idea (especially the class name). Why not use a std:;set? Or even a template function - something like:
template <typename T>
bool IsIn( T v, T a, T b, T c ) {
...
}
a modification for your comparison class is perhaps to use varargs, to make it general to n elements of the set.
精彩评论