Reputation: 122516
I want to write a class that takes a pair of iterators as parameters to the constructor, but I dont know how to raise an error at compile-time when those iterators' value_type
doesn't match an expected type. This is what I tried using typeid
:
#include <vector>
struct foo {
std::vector<double> data;
template <typename IT>
foo(IT begin, IT end){
typedef int static_assert_valuetype_is_double[
typeid(typename IT::value_type) == typeid(double) ? 1 : -1
];
std::cout << "constructor called \n";
data = std::vector<double>(begin,end);
}
};
int main()
{
std::vector<double> x(5);
foo f(x.begin(),x.end()); // double: ok
std::vector<int> y(10);
foo g(y.begin(),y.end()); // int: should not compile
}
Note that in this case, int
to double
would be fine, but thats just an example and in the real code the types have to match exactly. To my surprise in both cases, the constructor works without errors (there is only a warning about the unused typedef). Does the -1
sized array static assert trick not work when the typedef is declared inside a method? How do I produce an error when IT::value_type
is the wrong type?
PS: would be nice if there was an easy C++98 solution, but if this gets too complicated, I could also accept a C++11 solution.
Upvotes: 1
Views: 408
Reputation: 122516
Here is a C++98 compliant way to implement it.....
First the fun part: Implementing a is_same
is rather straightforward
template <typename T,typename U> struct is_same_type { static const bool value; };
template <typename T,typename U> const bool is_same_type<T,U>::value = false;
template <typename T> struct is_same_type<T,T> { static const bool value; };
template <typename T> const bool is_same_type<T,T>::value = true;
Now the not-so-fun part (C++11 really helps to statically assert without causing coworkers raising some eyebrows):
struct foo {
std::vector<double> data;
template <typename IT>
foo(IT begin, IT end) : data(begin,end) {
typedef int static_assert_valuetype_is_double[
is_same_type<double,typename IT::value_type>::value ? 1 : -1
];
std::cout << "constructor called \n";
}
};
int main(){
std::vector<double> x(5,2.3);
foo f(x.begin(),x.end());
for (std::vector<double>::iterator it = f.data.begin(); it != f.data.end();++it) std::cout << *it << " ";
//std::vector<int> y(10,3);
//foo g(y.begin(),y.end()); // THIS FAILS (AS EXPECTED)
}
As pointed out by others, I should actually be using std::iterator_traits<IT>::value_type
as not every iterator has a value_type
. However, in my case I rather want to restrict the possible iterators to a small set and disallowing iterators without a value_type
isnt a problem in my specific case.
Also note that the code in the question assigned to the member, while it is of course better to use the initializer list.
Upvotes: 1
Reputation: 40080
In modern C++, you could have used std::is_same
and static_assert
:
static_assert(std::is_same_v<typename std::iterator_traits<IT>::value_type, double>,
"wrong iterator");
See also std::iterator_traits
: an iterator it
is not guaranteed to have a value_type
typedef, and one should use std::iterator_traits<it>::value_type
instead.
In C++ 98, is_same
is trivial to implement, static_assert
needs a negative-size array trick or the BOOST_STATIC_ASSERT
.
Upvotes: 3
Reputation: 36607
For a solution that works in C++98 and later.....
#include <iterator>
template<class T> struct TypeChecker
{};
template<> struct TypeChecker<double>
{
typedef double valid_type;
};
template <typename IT>
void foo(IT begin, IT end)
{
typename TypeChecker<typename std::iterator_traits<IT>::value_type>::valid_type type_checker;
(void)type_checker;
// whatever
}
Instantiations of foo()
will succeed for any for an iterator for which value_type
is double
, and fail to compile otherwise.
The premise is that TypeChecker<x>
does not have a valid_type
for any x
other than double
, but we attempt to instantiate an instance of that type in foo()
. The (void)type_checker
prevents warnings, from some compilers about a variable that is never used, for valid types.
Upvotes: 1