2

I want a constructor to accept any iterator having x trait and referencing class y.

Class(std::iterator<std::random_access_iterator_tag, MyClass*> it);

But when I try to pass such iterator, the compilation fails with Candidate constructor not viable: no known conversion from 'iterator' (aka '__deque_iterator<value_type, pointer, reference, __map_pointer, difference_type, __block_size>') to 'std::iterator<std::random_access_iterator_tag, MyClass *>'.

The insertion code:

std::deque<MyClass*> collection_with_random_access_iterator{};
Class tmp(collection_with_random_access_iterator.begin());

What can I do here?

gx_
  • 4,690
  • 24
  • 31
Appleshell
  • 7,088
  • 6
  • 47
  • 96
  • It's worth noting that you get the error because (on your implementation at least) `deque::iterator` does not inherit from `iterator` (the Standard doesn't require it to do so). (Btw, a `MyClass**` is a kind of "random-access iterator" to `MyClass*` too (and e.g. `vector::iterator` can be just that), but raw pointers can't be derived from anything.) – gx_ Sep 07 '13 at 11:20
  • 1
    While we come at it.. std::iterator is not polymorphic, it's just an helper base class provided to simplify iterator definitions. It's not supposed to be used like that. – sbabbi Sep 07 '13 at 11:24
  • @sbabbi True, thanks for pointing that. Inheriting from the convenience struct `std::iterator`, even if publicly, is only an implementation detail. – gx_ Sep 07 '13 at 11:30
  • @gx_ Its a shame the c++ standard does not require the `iterators` of standard collections to be derived from a common parent. – Appleshell Sep 07 '13 at 14:44
  • @AdamS Pointers qualify as random-access iterators, and `vector::iterator` and `basic_string::iterator` can be `T *`. Anyway, inheritance wouldn't be relevant; you wouldn't want to use a virtual function to dereference an iterator. – Potatoswatter Sep 08 '13 at 00:03

2 Answers2

3

What about:

template<class Iterator>
Class(Iterator it,
  typename std::enable_if<
          std::is_same<
                  typename std::iterator_traits<Iterator>::value_type,
                  MyClass*
          >::value //checks for value_type
          &&
          std::is_base_of<
                  std::random_access_iterator_tag,
                  typename std::iterator_traits<Iterator>::iterator_category
          >::value //checks for iterator category
    >::type * = 0);

EDIT also you should consider replacing the first std::is_same with std::is_convertible, and check for const MyClass* if you are not going to modify the input.

sbabbi
  • 11,070
  • 2
  • 29
  • 57
  • Thank you, this looks like an option, but is also rather huge. I'd prefer a more compact solution, but I'll keep this in mind and come back if there is none. – Appleshell Sep 07 '13 at 10:29
  • @AdamS: The alternative is to accept any type, perhaps calling the template parameter something like `RandomIterator` to document its informal requirements. That makes the code simpler, but you'll get nastier error messages if the type doesn't meet the requirements. Alternatively, wait for "concepts" to be added to the language, hopefully next year. – Mike Seymour Sep 07 '13 at 11:08
  • You can make `enable_if` less verbose: http://flamingdangerzone.com/cxx11/2012/06/01/almost-static-if.html (source: http://stackoverflow.com/a/14623831 ) – gx_ Sep 07 '13 at 11:13
  • @Mike Seymour That could actually be good couple with static_assert (assuming this is the only template constructors in the class). – sbabbi Sep 07 '13 at 11:23
1

Iterators are usually accepted by value, then discriminated by delegation to other functions.

For example,

    template< typename iterator >
    Class( iterator it ) {
        init_class( * it, typename std::iterator_traits< iterator >::category() );
    }

    template< typename iterator >
    void init_class( iterator it, std::random_access_iterator_tag ) {
        for ( int i = 0; i != 42; i +=3 ) {
            do_something( it[ i ] );
        }
    }

    void do_something( MyClass * ) { … }
};

Passing the wrong iterator results in an error inside the function, which may be cryptic for the user. But that's what usually happens in a standard library implementation, and it's how iterators were originally used before SFINAE was invented. If there are common user mistakes, you can trap them specifically and lead users to specific errors/comments.

If don't need to select different behaviors, but want to make sure the user passed a random-access iterator over MyClass *, use a couple static_asserts with std::is_same conditions as sbabbi's answer. The resulting user experience is better than pure SFINAE because the error message says "Please pass a random-access iterator", not "No overload found."

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • It's worth noting that tag dispatching and/or static_assert cannot _always_ replace SFINAE, especially sometimes for constructors and interaction with traits like `is_constructible`. See http://flamingdangerzone.com/cxx11/2013/02/11/to-sfinae-or-not-to-sfinae.html – gx_ Sep 07 '13 at 11:38
  • @gx_ Right tool for the job. Is something wrong with this answer or did you run out of upvotes for today? ;v) – Potatoswatter Sep 07 '13 at 13:45
  • Nothing wrong, I just didn't think to vote. "Fixed" :) (it's not like you need more rep, but it's true that [voting is important](http://stackoverflow.com/help/why-vote)) – gx_ Sep 07 '13 at 14:41