5

I'm trying to create a new templated class "CrazyBucket< T >" which has to hold multiple values of type T.

I want to create a constructor for this class that can accept any 2 forward iterators as its arguments and copy in those values (of type T) to the class instance.

Constructor:

CrazyBucket< T >::CrazyBucket( iterator< forward_iterator_tag, T > start,  iterator< forward_iterator_tag, T > end )
{ ... }

But when I try to call it with,

vector< int > vec;
vec.push_back( 4 );
CrazyBucket< int > bucket( vec.begin(), vec.end() );

I get the following error,

candidate constructor not viable: no known conversion from 'iterator' (aka '__wrap_iter<pointer>') to 'std::iterator<std::forward_iterator_tag, int>' for 1st argument

Any help with how I should define my constructor is much appreciated.

Thanks in advance.

8
  • Check out this question - stackoverflow.com/questions/8751460/… Commented Jan 19, 2015 at 8:37
  • 1
    Why not something very simple like this: ideone.com/EU9FTK (If it is acceptable, I will post it as an answer). Commented Jan 19, 2015 at 8:43
  • @NickZavaritsky - thanks for the pointer. I might not be comprehending the post correctly, but that poster wants to limit the types of iterators being passed in? It doesn't talk about the data type the iterator points to (in my example, int ). Commented Jan 19, 2015 at 8:47
  • @PaulMcKenzie I think you should just post the answer, as that is the correct way of doing so. Commented Jan 19, 2015 at 8:48
  • 1
    @WhozCraig @NickZavaritsky I don't mind the level of the iterator (sorry I don't know the technical term) - i.e., any of the following would do forward, bi-directional, random-access. So I went with the lowest common denominator of forward iterators. But I do want to restrict the type of objects the iterators "point" to. I.e., to make sure they point to objects of type < T >. Commented Jan 19, 2015 at 8:58

2 Answers 2

2

You can use SFINAE to exclude non-matching types in a fashion I think is close to what you want.

#include <iostream>
#include <iterator>
#include <vector>

template<class T>
class CrazyBucket
{
public:
    template<class It, typename = typename std::enable_if<
        std::is_same< typename std::iterator_traits<It>::value_type,T>::value>::type>
    CrazyBucket(It beg, It end)
    {
        std::cout << __PRETTY_FUNCTION__ << '\n';
    }
};

int main()
{
    std::vector<int> vInt;
    CrazyBucket<int> cbOK(vInt.begin(), vInt.end());

    int ar[10];
    CrazyBucket<int> cbAlsoOK(ar, ar+10);

    // uncomment for failure test case.
    //std::vector<double> vDbl;
    //CrazyBucket<int> cbFail(vDbl.begin(), vDbl.end());
}

Also accomplished with a static assertion:

template<class It>
CrazyBucket(It beg, It end)
{
    static_assert(std::is_same<T, typename std::iterator_traits<It>::value_type>::value,
       "failed to match iterator value type");
    std::cout << __PRETTY_FUNCTION__ << '\n';
}

Either is restrictive, and you should know that may not be the end goal you had in mind. For example, an iteration of short will naturally store as int without data loss, yet this kind of SFINAE will toss it. That too can be overcome with more expansion, but by that time I think you need to consider whether it is really worth it in the end.

Anyway, best of luck.

Sign up to request clarification or add additional context in comments.

2 Comments

That was really helpful and informative! I made a small addition to the second code example to make sure the type of iterator was also satisfied (in this case, It is at least a forward iterator).
If somebody needs to do this at run-time, #include <typeinfo>, if( typeid( typename std::iterator_traits< IterType >::value_type ) != typeid( T ) ) will get the job done.
1

Does it really have to be the constructor? The problem as I see it is that you'd need to have a specialized constructor which isn't possible as per the standard. If you can defer your initialisation to a member function then the following approach will work:

template<class T>
struct CrazyContainer {

  template<class U>
  void init(U first,U last) {
    for(auto it=first;it!=last;it++) {
      // do stuff with 'it'
    }
  }
};

main() {
  std::vector<int> vec;

  CrazyContainer<int> f;
  f.init(vec.begin(),vec.end());
}

I'm looking forward to seeing if there's someone else that can come up with a way that permits this via a constructor.

Edit: Thanks to Sebastian for pointing out that a templated constructor will work just as well as the templated method:

template<class T>
struct CrazyContainer {

  template<class U>
  CrazyContainer(U first,U last) {
    for(auto it=first;it!=last;it++) {
      // do stuff
    }
  }
};


main() {
  std::vector<int> v;
  std::set<int> s;

  CrazyContainer<int> cv(v.begin(),v.end());
  CrazyContainer<int> cs(s.begin(),s.end());
}

2 Comments

You can use exactly the same code as a constructor, just replace void init with CrazyContainer. Why do you think that wouldn't work?
@Sebastian, you're right and thank you for pointing it out. I'll modify the answer to show that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.