2

The plan is to implement a class with two constructors. The first ctor should take a string-like type, the second ctor two iterators (begin and end) to a container of string-like objects. Internally, the class will work with const char*, null-terminated C strings. For the first ctor, that's an easy task where I can just be explicit. Passing in a C++ string with str.c_str() or similar string-like types works fine. For the second ctor however I'm not so sure how I should implement it. In general, it should be a LegcayInputIterator with a value type that is convertible to const char*. From the top of my head I can think of at least 4 types I want to support, and overloading the second ctor for so many seems like the wrong approach.

Why does my second ctor fail to compile? The ++, != and * operators should be defined. What would be a working approach to pass in two iterators to T but actually allow different string-like types from which I could get const char* (see example 5 in the code)?

The example code should illustrate my approach, but doesn't work unfortunately.

#include <vector>
#include <array>
#include <cstdio>
#include <string>
#include <iterator>

struct Example {

    using CtorIterator = std::iterator<std::input_iterator_tag, const char*>;
    Example(const char *text)
    {
        std::printf("Called single argument ctor, text = %s\n", text);
    }

    Example(CtorIterator begin, CtorIterator end)
    {
        std::printf("Called iterator ctor, text = [");
        for (auto it = begin; it != end; ++it)
            std::printf("%s, ", *it);
        std::printf("]\n");
    }
};

int main(int argc, char **argv) {
    Example ex1("Hello");
    std::string arg2("String");
    Example ex2(arg2.c_str());

    std::vector<const char*> args3{"A", "B", "C"};
    std::array<const char*, 3> args4{"D", "E", "F"};
    std::vector<std::string> args5{"G", "H", "I"};

    Example ex3(args3.begin(), args3.end());
    Example ex4(args4.begin(), args4.end());
    Example ex5(args5.begin(), args5.end()); // won't work

    return 0;
}
flowit
  • 1,382
  • 1
  • 10
  • 36

3 Answers3

2

The idiomatic way is to use templates instead:

template<typename IterT>
Example(IterT begin, IterT end) { ... }

Your way using std::iterator<...> doesn't work because few container iterators actually are of the type std::iterator<...>, and doesn't have a conversion operator to it.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • But even if containers would return exactly the iterator I'm using, why does the compiler complain about the operators used not being defined? A `std::iterator` should provide increment, inequality, dereferencing, ... – flowit Dec 31 '19 at 12:04
  • 3
    @flowit, `std::iterator` is a now deprecated struct that [was there](https://en.cppreference.com/w/cpp/iterator/iterator) to provide member types only for user-defined iterators derived from it. – Evg Dec 31 '19 at 12:06
2

std::iterator is not meant to be used that way. It was introduced into the library to support user-defined iterators by providing certain member types like iterator_category and value_type. std::iterator is an empty class and taking it by value is meaningless. Taking it by reference doesn't make much sense either, because iterators do not have to be derived from it. std::iterator is deprecated since C++17.

LegcayInputIterator is not a class, but a collection of requirements a type should satisfy. The type itself is not fixed and should be a template parameter. Since C++20, when concepts will become a language feature, you'll be able to write something like this:

template<typename InputIt> requires InputIterator<InputIt>
Example(It begin, It end) { ... }

or

template<InputIterator InputIt>
Example(It begin, It end) { ... }

to check that InputIt indeed satisfies the requirements of an input iterator. Here InputIterator is a concept, see here for a particular example. Until then you simply write:

template<typename InputIt>
Example(InputIt begin, InputIt end) { ... }

For example, std::vector's constructor from a range [first, last) is declared this way:

template<class InputIt>
vector(InputIt first, InputIt last, const Allocator& alloc = Allocator());
Evg
  • 25,259
  • 5
  • 41
  • 83
  • Concepts can't come soon enough, because I really don't want to play a lot with `enable_if` to limit this template ctor to meaningful arguments. However, a second question remains (I edited the original question to make it more clear): Now having an iterator to type T, how would I go about in making sure that I always get a `const char*` out of it? – flowit Dec 31 '19 at 13:23
  • @flowit, in your particular example you can write `&(*it)[0]` instead of `*it` to get `const char*` both for `const char*` and `std::string` underlying types. You can also write an iterator adapter that would return such a pointer when `operator*` is called. – Evg Dec 31 '19 at 13:47
0

It does not really address the question of why it does not work, but you could utilize std::begin and std::end

struct Example {

    Example(const char *text)
    {
        std::printf("Called single argument ctor, text = %s\n", text);
    }

    template<typename T>
    Example(const T &t)
    {

        std::printf("Called iterator ctor, text = [");
        for (auto it = std::begin(t); it != std::end(t); ++it)
            std::printf("%s, ", *it);
        std::printf("]\n");
    }
};

For relay completely on the range base for:

struct Example {  
    Example(const char *text)
    {
        std::printf("Called single argument ctor, text = %s\n", text);
    }

    template<typename T>
    Example(const T &t)
    {
        std::printf("Called iterator ctor, text = [");
        for (const auto &c : t)
            std::printf("%s, ", c);
        std::printf("]\n");
    }
};
t.niese
  • 39,256
  • 9
  • 74
  • 101
  • I think that's more a question of style, if I pass in a container or two iterators directly (or with C++20 maybe a range). The latter might be more flexible, so that's why I was going for it in the first place. Concepts probably make it easier to limit that template to actual container types. Right now it's just a whole lot of work. – flowit Dec 31 '19 at 13:13