std::enable_if 在模板参数上确定 STL 容器

Posted

技术标签:

【中文标题】std::enable_if 在模板参数上确定 STL 容器【英文标题】:std::enable_if on a template argument to determine STL container 【发布时间】:2016-03-25 18:18:36 【问题描述】:

基于an answer from Nawaz,我想使用 enable_if 来确定模板参数是否是容器,如果是,我想显示类型名称的自定义消息,而不是 typeid 中的名称。我以两种方式实现了模板专业化。代码编译并运行,但在任何情况下都不会调用专用方法。我假设我错误地使用了 enable_if,这里的正确应用程序是什么?

我在代码中的序列下面放置了一个独立的小控制台应用程序是: (a) 所需的包含文件 (b) 准备模板代码(使用 SFINAE) (c) 应该执行任务的结构的两个实现 (d) 一些客户端代码

#include <typeinfo>
#include <string>
#include <list>
#include <vector>
#include <iostream>
using namespace std;

template<typename T>
struct has_const_iterator

private:
    typedef char                      yes;
    typedef struct  char array[2];  no;

    template<typename C> static yes test(typename C::const_iterator*);
    template<typename C> static no  test(...);
public:
    static const bool value = sizeof(test<T>(0)) == sizeof(yes);
    typedef T type;
;

template <typename T>
struct has_begin_end

    template<typename C> static char(&f(typename std::enable_if<
        std::is_same<static_cast<typename C::const_iterator(C::*)() const>(&C::begin),
        typename C::const_iterator(C::*)() const>::value, void>::type*))[1];

    template<typename C> static char(&f(...))[2];

    template<typename C> static char(&g(typename std::enable_if<
        std::is_same<static_cast<typename C::const_iterator(C::*)() const>(&C::end),
        typename C::const_iterator(C::*)() const>::value, void>::type*))[1];

    template<typename C> static char(&g(...))[2];

    static bool const beg_value = sizeof(f<T>(0)) == 1;
    static bool const end_value = sizeof(g<T>(0)) == 1;
;
template<typename T>
struct is_container : std::integral_constant<bool,
    has_const_iterator<T>::value &&
    has_begin_end<T>::beg_value &&
    has_begin_end<T>::end_value>
 ;

struct TypeName 
    template <typename T>
    static const char* get() 
        return typeid(T).name();
    

    template <typename T, typename std::enable_if<is_container<T>::value>::type >
    static const char* get()
    
        typedef typename T::value_type ElementType;
        std:string containerType = "";
        if (std::is_same<decltype(std::vector<ElementType>), T>::value) 
            containerType = "(Vector) ";
        
        if (std::is_same<decltype(std::list<ElementType>), T>::value) 
            containerType = "(List) ";
        
        std::string returnString = "Container " + containerType;
        returnString += " of ";
        returnString += get<ElementType>();
        return returnString.c_str();
    
;

template <typename T> struct GypeName 

    static const char* get() 
        return typeid(T).name();
    

    template <class = typename std::enable_if<is_container<T>::value>::type >
    static const char* get()
    
        typedef typename T::value_type ElementType;
        std:string containerType = "";
        if (std::is_same<decltype(std::vector<ElementType>), T>::value) 
            containerType = "(Vector) ";
        
        if (std::is_same<decltype(std::list<ElementType>), T>::value) 
            containerType = "(List) ";
        
        std::string returnString = "Container " + containerType;
        returnString += " of ";
        returnString += GypeName<ElementType>::get();
        return returnString.c_str();
    
;



int main(int argc, char** argv) 
    cout << is_container<int>::value << endl;
    cout << is_container<std::vector<int>>::value << endl;
    cout << TypeName::get<int>() << endl;
    cout << TypeName::get<std::string>() << endl;
    cout << TypeName::get<std::vector<int>>() << endl;
    cout << TypeName::get<std::vector<std::vector<int>>>() << endl;
    cout << GypeName<int>::get() << endl;
    cout << GypeName<std::string>::get() << endl;
    cout << GypeName<std::vector<int>>::get() << endl;
    cout << GypeName<std::vector<std::vector<int>>>::get() << endl;
    return 0;

这一切的输出是

0
1
int
class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
class std::vector<int,class std::allocator<int> >
class std::vector<class std::vector<int,class std::allocator<int> >,class std::allocator<class std::vector<int,class std::allocator<int> > > >
int
class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
class std::vector<int,class std::allocator<int> >
class std::vector<class std::vector<int,class std::allocator<int> >,class std::allocator<class std::vector<int,class std::allocator<int> > > >

在这两种情况下都不调用专用函数。

【问题讨论】:

提供的代码中存在拼写错误/编译器错误,Demo 已修复,(但 UB 未修复(从临时返回 const char*)。 代码需要 boost 并使用 C++11 (visual studio 2015) - 我将删除引用并提供一些独立的内容。 好的,主要问题是 enable_if 应该是返回类型而不是模板参数。谢谢 Jarod42。 【参考方案1】:

您可以使用以下内容:

struct TypeName 
    template <typename T>
    static
    std::enable_if_t<!is_container<T>::value, const char*>
    get() 
        return typeid(T).name();
    

    template <typename T>
    static
    std::enable_if_t<is_container<T>::value, std::string>
    get()
    
        typedef typename T::value_type ElementType;
        std::string containerType = "";
        if (std::is_same<std::vector<ElementType>, T>::value) 
            containerType = "(Vector) ";
        
        if (std::is_same<std::list<ElementType>, T>::value) 
            containerType = "(List) ";
        
        return (boost::format("Container %s of %s")
                % containerType
                % TypeName::get<ElementType>()).str();
    
;

Demo

请注意,std::string 被视为char 的容器。 由于您对向量/列表有特定的 (runtime :( ) 案例,您可以只对这两个使用专门化:

namespace detail

    template <typename T> struct TypeName
    
        auto operator ()() const  return typeid(T).name();   
    ;

    template <template <typename...>class C,  typename T, typename...Ts>
    struct TypeName<C<T, Ts...>>
    
        auto operator()() const 
            return (boost::format("container of %s") % TypeName<T>()).str();
        
    ;

    template <typename T, typename A>
    struct TypeName<std::vector<T, A>>
    
        auto operator()() const 
            return (boost::format("Vector of %s") % TypeName<T>()).str();
        
    ;

    template <typename T, typename A>
    struct TypeName<std::list<T, A>>
    
        auto operator()() const 
            return (boost::format("List of %s") % TypeName<T>()).str();
        
    ;



struct TypeName 
    template <typename T>
    static auto get() 
        return detail::template TypeName<T>();
    
;

Demo

【讨论】:

以上是关于std::enable_if 在模板参数上确定 STL 容器的主要内容,如果未能解决你的问题,请参考以下文章

std::enable_if与boost::enable_if,boost::enable_if_c的区别与联系

std::enable_if与boost::enable_if,boost::enable_if_c的区别与联系

std::enable_if与boost::enable_if,boost::enable_if_c的区别与联系

具有 std::array 大小类型的派生模板类

关于SFINAE的功能和结构之间的差异

Visual Studio 2013 - std::enable_if 警告 4544