簡體   English   中英

檢查變量類型是否可迭代?

[英]Check if a variable type is iterable?

有沒有辦法檢查任意變量類型是否可迭代?

所以要檢查它是否有索引元素,或者我實際上可以遍歷它的子元素? (例如使用 foreach?)

是否可以為此創建通用模板?

我在搜索時發現了其他編程語言的技術。 然而仍然需要找出如何在 C++ 中做到這一點。

您可以為此創建一個特征:

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

活生生的例子

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() constend() const ,因此,即使以下內容也被驗證為可迭代的:

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

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

你可以在這里看到這些片段

如果您在 C++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類型,因此intunsigned更好的匹配,得到true
  • 我選擇42因為它是所有問題的答案,靈感來自 Eric Niebler 的范圍實現。

在您的情況下, C++11具有適用於數組和容器的自由函數std::beginstd::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; }

如果您需要更多的通用性,表達某事物可迭代的方法還可以包括用戶定義的類型,這些類型為beginend帶來自己的重載,因此您需要在此處應用一些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; }

您可以使用此技術來獲得更適合您實際情況的解決方案。

是的,使用這個特性類兼容

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

解釋

  • 如果C::end()存在,則check<C>(0)調用check(int,const_iterator)並返回const_iterator兼容類型
  • else check<C>(0)調用check(...) (參見省略號轉換
  • sizeof(check<C>(0))取決於這些函數的返回類型
  • 最后,編譯器將常valuetruefalse

查看在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

注意: C++11(和 C++14)提供了許多特性類,但沒有關於可迭代性......

另請參閱jrokJarod42 的類似答案。

這個答案在公共領域 - CC0 1.0 Universal

這取決於您所說的“可迭代”是什么意思。 它在 C++ 中是一個松散的概念,因為您可以以多種不同的方式實現迭代器。

如果通過foreach您指的是 C++11 的基於范圍的 for 循環,則該類型需要定義begin()end()方法並返回響應operator!=operator++operator*迭代器。

如果您指的是 Boost 的 BOOST_FOREACH 助手,請參閱BOOST_FOREACH Extensibility

如果在您的設計中您有一個所有可迭代容器都繼承自的通用接口,那么您可以使用 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

或者,如果(像我一樣)你討厭每個 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()成員函數。

如果您想要更健壯/徹底/可重用的東西,請改用其他出色的建議解決方案之一。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM