0

Based on an answer from Nawaz I want to use enable_if to determine whether a template argument is a container or not and if it is I want to display a custom message for the type name instead of the name from the typeid. I have implemented the template specialization in two ways. The code compiles and runs but in neither case does the specialized method get called. I assume I am using enable_if wrongly, what is the correct application here?

I put a self-contained little console application underneath the sequence in the code is: (a) required include files (b) The preparatory template code (using SFINAE) (c) two implementations of the struct that are supposed to execute the task (d) some client code

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

The output of all this is

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

Not calling the specialized function in either case.

Community
  • 1
  • 1
crogg01
  • 2,446
  • 15
  • 35
  • There are typos/compiler errors in provided code, [Demo](http://coliru.stacked-crooked.com/a/774f27cddb6363be) once fixed, (but UB not fixed (return const char* from temporary)). – Jarod42 Mar 25 '16 at 18:39
  • The code requires boost and uses C++11 (visual studio 2015) - I will remove the reference and give something self-contained. – crogg01 Mar 25 '16 at 18:43
  • OK, main issue was enable_if should be on the return type not the template arguments. Thanks Jarod42. – crogg01 Mar 25 '16 at 23:38

1 Answers1

1

You may use the following:

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

Note that std::string is considered as a container of char.
As you have specific (runtime :( ) case for vector/list, you may just use specialization for these two instead:

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

Jarod42
  • 203,559
  • 14
  • 181
  • 302