463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122516

How to allow only iterators with a ceratin value_type?

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

Answers (3)

463035818_is_not_an_ai
463035818_is_not_an_ai

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

YSC
YSC

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

Peter
Peter

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

Related Questions