Kontener STL z określonym typem jako argument ogólny

25

Czy jest jakiś sposób, w jaki mogę stworzyć funkcję, która przyjmuje kontener określonego typu (powiedzmy std::string) jako parametr

void foo(const std::container<std::string> &cont)
{
   for(std::string val: cont) {
      std::cout << val << std::endl;
   }
}

i nazwać to dla każdego typu kontenera STL jako dane wejściowe? jak wyżej?

std::set<std::string> strset;
std::vector<std::string> strvec;
std::list<std::string> strlist;

foo(strset);
foo(strvec);
foo(strlist);
chatzich
źródło
2
Tak, nazywa się to funkcją szablonu. ;)
Ulrich Eckhardt
2
Często uważa się, że lepiej jest przekazać parę iteratorów (reprezentujących odpowiednio początkowy i jeden za końcem kontenera). Tak długo, jak iteratory spełniają wymagania funkcji, często (z pewnymi wyjątkami) nie ma znaczenia, z jakiego typu kontenerów zostały uzyskane.
Peter

Odpowiedzi:

21

Można utworzyć fooszablon funkcji, biorąc parametr szablonu szablonu dla typu kontenera.

na przykład

template<template<typename...> typename C>
void foo(const C<std::string> &cont)
{
   for(std::string val: cont) {
      std::cout << val << std::endl;
   }
}

RELACJA NA ŻYWO

songyuanyao
źródło
Myślę, że możemy to jeszcze bardziej uogólnić. Zobacz moją odpowiedź.
theWiseBro
Odpowiedź Larsa jest lepsza, ponieważ działa również z tablicami w stylu C.
Ayxan,
1
@ theWiseBro Tak, to ogólnie dobry pomysł. Ale myślę, że OP chce używać go tylko z określonym typem std::string, więc ..
songyuanyao
3
@ dokładnie theWiseBro. OP powiedział, że powinien działać z jednym konkretnym typem . Dlatego nie ma korzyści z dalszego uogólnienia.
M. Spiller,
1
@ theWiseBro Rozumiem, co miałeś na myśli. Nie jestem pewien pierwotnych zamiarów OP, powiedział tylko, że chcę jednego konkretnego typu; być może będziesz musiał wyjaśnić to OP. :)
songyuanyao
6

W zależności od tego, czy chcesz przeciążać fooinne przypadki, czy nie

// Doesn't participate in overload resolution when not applicable
template<typename Container, typename = std::enable_if_t<std::is_same_v<typename Container::value_type, std::string>>>
void foo(const Container &cont) {
   for(std::string val: cont) {
      std::cout << val << std::endl;
   }
}

// simpler
template<typename Container>
void foo(const Container &cont) {
   static_assert(std::is_same_v<typename Container::value_type, std::string>, "Container must contain std::string")
   for(std::string val: cont) {
      std::cout << val << std::endl;
   }
}

Możesz użyć innego testu std::is_same, na przykład std::is_convertiblezezwalać

std::vector<char *> c_strings;
foo(c_strings);
Caleth
źródło
0

Zamiast tego możesz rozważyć użycie iteratorów. Wynik pośredni może wyglądać

template<typename Iter>
void foo(Iter begin, Iter end) {
  using T = decltype(*begin);
  std::for_each(begin, end, [] (cons T & t) {
    std::out << t << '\n';
  }
}

Teraz za pomocą szablonu na żądanie:

template<typename Iter, typename Callable>
void foo(Iter begin, Iter end, Callable & c) {
  std::for_each(begin, end, c);
}

Właśnie nauczyliśmy się korzystać z tego, co już oferuje STL.

użytkownik1624886
źródło
-1

Dodając do odpowiedzi @ songyuanyao, myślę, że możemy ją uogólnić dalej:

template<template<typename...> typename C, typename ... D>
void foo(const C<D...> &cont)
{
   for(const auto& val: cont) {
      std::cout << val << std::endl;
   }
}
theWiseBro
źródło
1
Nie ogranicza to typu elementu do std :: string, więc nie odpowiada na pytanie.
Sasha
@Sasha To prawda, że ​​nie jest to ustawione na std :: string, ale jest bardziej uogólnione. OP chce użyć określonego typu. Powiedz dzisiaj, że używa std :: string, a jutro chce zamiast tego użyć MyCustomString. Czy nie byłoby to łatwiejsze w utrzymaniu, ponieważ musi on edytować kod tylko w jednym miejscu?
theWiseBro
Ale to nie pokazuje, jak ograniczyć je do obu std :: string lub elementów MyCustomString - a querent specjalnie chciał wziąć „pojemnik z konkretnym typem ”. Tak jak jest, zaakceptuje każdy typ, który akurat jest szablonem, i w tym momencie, dlaczego nie po prostu utworzyć szablonu na pojedynczym <typename C> zamiast tego? Jest to o wiele prostsze i nieco bardziej uogólnione - np. Twój zajmie std :: string (aka std :: basic_string <char>) jako kontener, ale nie niestandardową strukturę MyCustomString, więc nie jest w pełni ogólny.
Sasha
A jeśli funkcja oczekuje, że elementy będą std :: string, zezwolenie użytkownikom na przekazanie std :: tuple <int, double, std :: nullptr_t> utrudnia korzystanie i utrzymanie.
Sasha
@Sasha hmm. Rozumiem co masz na myśli. To prawda. Dzięki za heads-up!
theWiseBro