Проверьте, является ли переменная переменной? - программирование
Подтвердить что ты не робот

Проверьте, является ли переменная переменной?

Есть ли способ проверить, является ли произвольный тип переменной итерабельным?

Итак, чтобы проверить, есть ли у него индексированные элементы, или я могу на самом деле перебрать его через дочерние элементы? (Например, используйте foreach?)

Можно ли создать для этого универсальный шаблон?

Я нашел методы для других языков программирования, ища его. Однако все же нужно выяснить, как это сделать на С++.

4b9b3361

Ответ 1

Это зависит от того, что вы подразумеваете под "итеративным". В C++ эта концепция довольно свободна, поскольку вы можете реализовать итераторы разными способами.

Если в foreach вы ссылаетесь на циклы C++ 11 для диапазона, типу необходимо определить методы begin() и end() и возвращать итераторы, которые отвечают на operator!=, operator++ и operator*.

Если вы имеете в виду Boost BOOST_FOREACH помощник, то смотрите Расширяемость BOOST_FOREACH.

Если в вашем проекте есть общий интерфейс, от которого наследуются все итерируемые контейнеры, вы можете использовать C++ 11 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

Ответ 2

Вы можете создать для этого черту:

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));

Живой пример.

Ответ 3

Да, используя класс признаков С++ 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) }; 
};

Объяснение

  • check<C>(0) вызывает check(int,const_iterator), если C::end() существует и возвращает const_iterator совместимый тип
  • else check<C>(0) вызывает check(...) (см. преобразование многоточия)
  • sizeof(check<C>(0)) зависит от типа возврата этих функций
  • наконец, компилятор устанавливает константу value в true или false

См. компиляцию и пробный запуск 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';
}

Выход

set=1
int=0

Примечание. С++ 11 (и С++ 14) предоставляет множество классов признаков, но ни о каких iterablility...

См. также похожие ответы jrok и Jarod42.

Этот ответ находится в общедоступном домене - CC0 1.0 Universal

Ответ 4

У cpprefence есть пример ответа на ваш вопрос. Он использует SFINAE, вот немного измененная версия этого примера (в случае, если содержание этой ссылки изменяется со временем):

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;

Теперь, как это можно использовать

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';

Выход:

true
true
false
false

Сказав это, все, что он проверяет, это объявление begin() const и end() const, поэтому, соответственно, даже следующее проверяется как повторяемое:

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

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

Вы можете увидеть эти кусочки вместе здесь

Ответ 5

Невозможно проверить, является ли произвольный тип переменной итерабельным. Хотя, есть некоторые общие типы, для которых это можно было бы сделать, но проверка будет во время компиляции и не запускать время.

Контейнеры стиля STL в С++ выставляют участников начинать и заканчивать. Присутствие этих членов может быть использовано для определения того, что тип, вероятно, итерабельен.

Примитивный тип массива в С++ представляет собой небольшую ошибку. Адрес первого действительного индекса и адрес, следующий за последним действительным индексом, можно использовать как действительные начинающие и конечные итераторы. Однако массивы С++ не выставляют свою длину; вам нужно сохранить это в отдельной переменной. Таким образом, для определения "конечного итератора" массива вам нужны две части информации: адрес первого индекса и длина массива. (На самом деле вам также нужна третья часть информации: ширина байта каждого индекса, но это обычно выводится из самого массива).

Ответ 6

Или, если (как и я) вы ненавидите каждое решение SFINAE, являющееся большим блоком фиктивных определений структур с бессмысленной ::type и ::value ерундой, вот пример использования быстрой и (очень) грязной однострочной:

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

Последний аргумент шаблона выполняет несколько действий за один шаг:

  1. Проверяет, имеет ли тип функцию-член begin() или эквивалентную.
  2. Проверяет, что функция begin() возвращает что-то с определенным operator*() (типично для итераторов).
  3. Определяет тип, полученный в результате отмены ссылки на итератор, и сохраняет его на случай, если он пригодится в реализации вашего шаблона.

Ограничение: не перепроверять наличие соответствующей функции-члена end().

Если вы хотите что-то более надежное/тщательное/многократно используемое, используйте вместо этого одно из других превосходных предложенных решений.

Ответ 7

Если вы находитесь под эгидой С++ 11 и выше, то одним из обычных способов проверки SFINAE, который работает, когда вам нужно специализироваться только для одного свойства, является следующий:

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 */ };

Хитрость заключается в следующем:

  • Когда выражение не работает, будет создана только вторая специализация, потому что первая не сможет скомпилироваться и sfinae закончится. Итак, вы получите false.
  • Когда выражение работает, обе перегрузки являются кандидатами, поэтому я вынужден усилить специализацию. В этом случае 42 имеет тип int, и поэтому int является лучшим соответствием, чем unsigned, получая true.
  • Я принимаю 42, потому что это ответ на все вопросы, вдохновленный реализацией диапазона Эрика Ниблера.

В вашем случае C++11 имеет свободные функции std::begin и std::end, которые работают для массивов и контейнеров, поэтому должно работать следующее выражение:

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; }

Если вам нужно больше общности, способ выражения того, что что-то итеративное, может также включать определяемые пользователем типы, которые вносят свои собственные перегрузки для begin и end, поэтому вам нужно применить некоторые adl здесь:

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; }

Вы можете поиграть с этой техникой, чтобы найти решения, которые лучше соответствуют вашему фактическому контексту.