4

I know I can use a template to make a constructor which accepts any type of iterator like this:

struct Thing
{
  std::vector<int> integers;
  std::list<std::string> strings;

  template <typename InputIt>
  Thing(InputIt start, InputIt end): integers(start, end) {}
};

Or, I can make constructors that accept the specific iterator type of the containers I'm using:

struct Thing
{
  std::vector<int> integers;
  std::list<std::string> strings;

  Thing(std::vector<int>::iterator start, std::vector<int>::iterator end): integers(start, end) {}
  Thing(std::list<std::string>::iterator start, std::list<std::string>::iterator end): strings(start, end) {}
};

I would like to do the above, but I don't want the iterators to be restricted to a specific type of container.

I imagine having some types IntInputIt and StringInputIt that can only refer to int or std::string iterators, like this:

struct Thing
{
  std::vector<int> integers;
  std::list<std::string> strings;

  Thing(IntInputIt start, IntInputIt end): integers(start, end) {}
  Thing(StringInputIt start, StringInputIt end): strings(start, end) {}
};

Then I could initialize my struct from any kind of list:

std::vector<int> ints1({1,2,3});
std::list<int> ints2({4,5,6});
auto thing1 = Thing(ints1.begin(), ints1.end());
auto thing2 = Thing(ints2.begin(), ints2.end());

Is there a straightforward way to accomplish my imagined IntInputIt and StringInputIt?

2 Answers 2

11

With C++20 concepts you can easily do this with:

#include <iterator>

class Thing
{
  std::vector<int> integers;
  std::list<std::string> strings;

public:
  template<std::input_iterator InputIt>
    requires std::same_as<std::iter_value_t<InputIt>, int>
  Thing(InputIt start, InputIt end): integers(start, end) {}

  template<std::input_iterator StringInputIt>
    requires std::same_as<std::iter_value_t<StringInputIt>, std::string>
  Thing(StringInputIt start, StringInputIt end): strings(start, end) {}
};
Sign up to request clarification or add additional context in comments.

2 Comments

And without C++20, you can painfully do this :)
std::same_as is often overconstraining. There's no reason why you couldn't be more permissive with std::convertible_to<..., int> when constructing a range of ints.
0

With c++17 SFINAE it's not so painfully as @passer-by mentioned.

#include <list>
#include <vector>
#include <iterator>
#include <type_traits>

struct Thing {
    std::vector<int> integers;
    std::list<std::string> strings;

    template <typename InputIt,
              std::enable_if_t<std::is_same_v<
                  int, typename std::iterator_traits<InputIt>::value_type>, int> = 0>
    Thing(InputIt start, InputIt end) : integers(start, end) {}

    template <
        typename InputIt,
        std::enable_if_t<std::is_same_v<
            std::string, typename std::iterator_traits<InputIt>::value_type>, int> = 0>
    Thing(InputIt start, InputIt end) : strings(start, end) {}
};

int main() {
    std::list<int> i = {1, 2, 3, 4};
    std::list<std::string> s = {"Hello", "a", "beatifull", "world", "!"};
    std::list<double> d = {42.0, 12.12};

    auto ints_thing = Thing{std::begin(i), std::end(i)};
    auto strings_thing = Thing{std::begin(s), std::end(s)};
    // auto doubles_thing = Thing{std::begin(d), std::end(d)};
}

Play with this snippet in godbolt. It is also possible to replace std::is_same_v with std::is_convertible_v depends of your requirements.

Comments

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.