Sprawdzić, czy typ zmiennej jest iterowalny?

Czy Jest jakiś sposób, aby sprawdzić, czy dowolny typ zmiennej jest iterowalny?

Więc, aby sprawdzić, czy ma indeksowane elementy lub mogę rzeczywiście pętli nad jego dzieci? (Użyj na przykład foreach?)

Czy jest możliwość stworzenia uniwersalnego szablonu do tego?

Znalazłem techniki dla innych języków programowania, szukając go. Jednak nadal trzeba dowiedzieć się, jak to zrobić w C++.

Author: xskxzr, 2012-12-11

6 answers

To zależy od tego, co masz na myśli mówiąc "iterable". Jest to luźna koncepcja w C++ , ponieważ można zaimplementować Iteratory na wiele różnych sposobów.

Jeśli przez foreach odnosisz się do pętli opartych na zakresach C++11, typ wymaga zdefiniowania metod begin() i end() oraz zwrócenia iteratorów odpowiadających operator!=, operator++ i operator*.

Jeśli masz na myśli Boost ' S boost_foreach helper, zobacz Boost_foreach Extensibility.

Jeśli w Twoim projekcie masz wspólny interfejs, który wszystkie iterowalne kontenery dziedziczą z, wtedy możesz użyć std:: is_base_of:

struct A : IterableInterface {}
struct B {}
template <typename T>
constexpr bool is_iterable() {
    return std::is_base_of<IterableInterface, T>::value;
}
is_iterable<A>(); // true
is_iterable<B>(); // false
 6
Author: Reuben Morais,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-11 23:42:44

Możesz stworzyć dla tego cechę:

namespace detail
{
    // To allow ADL with custom begin/end
    using std::begin;
    using std::end;

    template <typename T>
    auto is_iterable_impl(int)
    -> decltype (
        begin(std::declval<T&>()) != end(std::declval<T&>()), // begin/end and operator !=
        void(), // Handle evil operator ,
        ++std::declval<decltype(begin(std::declval<T&>()))&>(), // operator ++
        void(*begin(std::declval<T&>())), // operator*
        std::true_type{});

    template <typename T>
    std::false_type is_iterable_impl(...);

}

template <typename T>
using is_iterable = decltype(detail::is_iterable_impl<T>(0));

Przykład na żywo .

 33
Author: Jarod42,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-11 07:43:17

Cpprefence ma przykład odpowiadający na twoje pytanie . Jest to użycie SFINAE, tutaj jest nieco zmodyfikowana wersja tego przykładu (w przypadku, gdy zawartość tego linku zmienia się w czasie):

template <typename T, typename = void>
struct is_iterable : std::false_type {};

// this gets used only when we can call std::begin() and std::end() on that type
template <typename T>
struct is_iterable<T, std::void_t<decltype(std::begin(std::declval<T>())),
                                  decltype(std::end(std::declval<T>()))
                                 >
                  > : std::true_type {};

// Here is a helper:
template <typename T>
constexpr bool is_iterable_v = is_iterable<T>::value;

Teraz, tak można go używać

std::cout << std::boolalpha;
std::cout << is_iterable_v<std::vector<double>> << '\n';
std::cout << is_iterable_v<std::map<int, double>> << '\n';
std::cout << is_iterable_v<double> << '\n';
struct A;
std::cout << is_iterable_v<A> << '\n';

Wyjście:

true
true
false
false

Powiedziawszy, że wszystko, co sprawdza, to deklaracja begin() const i end() const, więc odpowiednio, nawet następujące jest weryfikowane jako iterowalne:

struct Container
{
  void begin() const;
  void end() const;
};

std::cout << is_iterable_v<Container> << '\n'; // prints true

Możesz zobaczyć te kawałki razem tutaj

 11
Author: aniliitb10,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-29 05:49:46

Yes using this traits class compatible c++03

template<typename C>
struct is_iterable
{
  typedef long false_type; 
  typedef char true_type; 
    
  template<class T> static false_type check(...); 
  template<class T> static true_type  check(int, 
                    typename T::const_iterator = C().end()); 
    
  enum { value = sizeof(check<C>(0)) == sizeof(true_type) }; 
};

Wyjaśnienie

  • check<C>(0) wywołuje check(int,const_iterator) Jeśli C::end() istnieje i zwraca const_iterator zgodny typ
  • else check<C>(0) calls check(...) (Zobacz ellipsis conversion )
  • sizeof(check<C>(0)) zależy od typu zwracanego tych funkcji
  • wreszcie kompilator ustawia stałą value na true lub false

Zobacz kompilację i testowanie na coliru

#include <iostream>
#include <set>

int main()
{
    std::cout <<"set="<< is_iterable< std::set<int> >::value <<'\n';
    std::cout <<"int="<< is_iterable< int           >::value <<'\n';
}

Wyjście

set=1
int=0

Notatka: C++11 (i C++14) dostarcza wiele klas cech , ale Żadna O iterablościowości...

Zobacz też podobne odpowiedzi z jrok i Jarod42 .

ta odpowiedź jest w domenie publicznej - CC0 1.0 Universal

 6
Author: oHo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Jeśli jesteś pod parasolem C++11 i nie tylko, jednym z typowych sposobów sprawdzania SFINAE, który działa, gdy musisz specjalizować się tylko w jednej właściwości, jest następujący:]}

template<class T, class = decltype(<expression that must compile>)>
inline constexpr bool expression_works(int) { return true; }

template<class>
inline constexpr bool expression_works(unsigned) { return false; }

template<class T, bool = expression_works<T>(42)>
class my_class;

template<class T>
struct my_class<T, true>
{ /* Implementation when true */ };

template<class T>
struct my_class<T, false>
{ /* Implementation when false */ };

Sztuczka jest następująca:

  • gdy wyrażenie nie działa, tylko druga specjalizacja zostanie utworzona instancyjnie, ponieważ pierwsza nie zostanie skompilowana i sfinae zostanie odtworzone. Więc dostajesz false.
  • gdy wyrażenie działa, oba przeciążenia są kandydatami, więc muszę wymusić lepsza specjalizacja. W tym przypadku 42 mA typ int, a zatem int jest lepszym dopasowaniem niż unsigned, otrzymanie true.
  • biorę 42 ponieważ jest to odpowiedź na wszystko , zainspirowana implementacją zakresu Erica Nieblera.

W Twoim przypadku, C++11 ma wolne funkcje std::begin i std::end, które działają dla tablic i kontenerów, więc wyrażenie, które musi działać to:

template<class T, class = decltype(std::begin(std::declval<T>()))
inline constexpr bool is_iterable(int) { return true; }

template<class>
inline constexpr bool is_iterable(unsigned) { return false; }

Jeśli potrzebujesz więcej ogólności, sposób na wyrażenie, że coś jest iterowalne może również zawierać typy zdefiniowane przez Użytkownika, które powodują własne przeciążenia dla begin i end, więc musisz zastosować niektóre adl tutaj:

namespace _adl_begin {
    using std::begin;

    template<class T>
    inline auto check() -> decltype(begin(std::declval<T>())) {}
}

template<class T, class = decltype(_adl_begin::check<T>())>
inline constexpr bool is_iterable(int) { return true; }

template<class>
inline constexpr bool is_iterable(unsigned) { return false; }

Możesz bawić się tą techniką, aby osiągnąć rozwiązania, które lepiej pasują do twojego rzeczywistego kontekstu.

 6
Author: Peregring-lk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-05 19:27:02

Lub jeśli (tak jak ja) nienawidzisz każdego rozwiązania SFINAE jako dużego bloku atrapowych definicji struktur z ::type i ::value nonsensem do przebrnięcia, oto przykład użycia szybkiej i (bardzo) brudnej jednowierszówki:

template <
    class Container,
    typename ValueType = decltype(*std::begin(std::declval<Container>()))>
static void foo(Container& container)
{
    for (ValueType& item : container)
    {
        ...
    }
}

Ostatni argument szablonu robi wiele rzeczy w jednym kroku:

  1. sprawdza, czy typ ma begin() funkcję członka, lub jej odpowiednik.
  2. sprawdza, czy funkcja begin() zwraca coś, co ma operator*() zdefiniowane (typowe dla Iteratory).
  3. określa typ, który wynika z odwoływania się do iteratora i zapisuje go na wypadek, gdyby był przydatny w implementacji szablonu.

Ograniczenie: nie sprawdza dwukrotnie, czy istnieje pasująca funkcja członka end().

Jeśli chcesz czegoś bardziej solidnego / dokładnego / wielokrotnego użytku, wybierz jedno z innych doskonałych proponowanych rozwiązań.

 0
Author: BTJ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-29 16:21:50