How to Write a Type Trait 'Is_Container' or 'Is_Vector'

type_traits for std Container?

It does not exist.

You can create your own trait if you know the set of container types that should be supported :

template<class T>
struct is_container
{
static const bool value = false;
};

template<>
template<class T, class Alloc>
struct is_container<std::vector<T, Alloc>>
{
static const bool value = true;
};

// ... same specializations for other containers.

And you use it like other traits:

cout << is_container<std::vector<int>>::value << endl;
cout << is_container<int>::value << endl;

See it here.

Note that usually you should pass iterators to your functions, not containers. So you keep your code container-independent and much more generic.

How to write a type trait method

The template parameter T of member templates shadows the template parameter T of class template. Give them another name; and specify default value for template parameter of min(), otherwise they can't be deduced. e.g.

template<typename X>
using isComplex = std::is_same<X, std::complex<typename X::value_type>>;

template <typename X = T>
typename std::enable_if<isComplex<X>::value>::type min() {
std::cout << "Min for complex" << std::endl;
}

template <typename X = T>
typename std::enable_if<std::is_arithmetic<X>::value>::type min() {
std::cout << "Min for arithmetic values." << std::endl;
}

LIVE

Determine if a type is an STL container at compile time

First, you define your primary template, which will have a member which is false in the default case:

template <typename T>
struct is_cont {
static const bool value = false;
};

Then you will define partial specializations for your container types which have a value of true instead:

template <typename T,typename Alloc>
struct is_cont<std::vector<T,Alloc> > {
static const bool value = true;
};

Then for a type X that you want to check, use it like

if (is_cont<X>::value) { ... } 

sfinae to detect containers: failure for std:array

This is not SFINAE but regular template specialisation. Your std::array is not recognised because a value of type std::size_t (which ist std::array's second argument) is not a typename.

You can change your check for array specifically:

template <typename T, std::size_t N> struct is_container<std::array<T,N>> : std::true_type { };
template <typename... Ts> struct is_container<std::vector<Ts...>> : std::true_type { };

If you actually want to use SFINAE to check for anything that behaves like a container, you could check for the existance of std::begin, std::end, std::size for that type.

How to specialize template for containers and enums

The reason that the second call to the default implementation is very simple.

Simply work out by hand how the parameters get deduced for the container version template's parameters:

template<typename Container>
struct wrapper<Container, typename Container::value_type>

You are instantiating the following template:

wrapper<std::vector<int>>

So:

1) Container is a std::vector<int>

2) Container::value_type is int

Therefore this specialization becomes:

struct wrapper<std::vector<int>, int>

However you are invoking only:

wrapper<std::vector<int>, void>

because void is the default value for the second template parameter, so this matches the wrong specialization.

The solution is very simple, the container specialization should simply be:

#include <type_traits>

template<typename Container>
struct wrapper<Container, std::void_t<typename Container::value_type>> {

std::void_t is C++17, there are other question on stackoverflow.com that explain how to implement it for earlier C++ standards. Complete example:

#include <vector>
#include <iostream>
#include <type_traits>

enum En {};

template <typename T, typename = void>
struct wrapper {
static T getValue()
{
std::cout<<"\n WRAPPER DEFAULT VERSION IS CALLED.\n";
return T();
}
};

template<typename T>
struct wrapper<T,typename std::enable_if<std::is_enum<T>::value>::type>{
static T getValue()
{
std::cout<<"\n WRAPPER ENUM VERSION IS CALLED.\n";
return T();
}
};

template<typename Container>
struct wrapper<Container, std::void_t<typename Container::value_type>> {
static Container getValue()
{
std::cout<<"\n WRAPPER CONTAINER VERSION IS CALLED.\n";
return Container();
}
};


int main()
{
//En is an enum type
En en = (En) wrapper<En>::getValue(); //Prints ENUM VERSION

std::vector<int> vec;
vec = wrapper<std::vector<int>>::getValue(); //Prints DEFAULT VERSION
}

Result:

WRAPPER ENUM VERSION IS CALLED.

WRAPPER CONTAINER VERSION IS CALLED.

How to implement is_enum_class type trait?

Based on your +T{} test:

Option #1:

Expression SFINAE in trailing return type:

#include <type_traits>

template <typename T>
auto test(int) -> decltype((void)+T{}, std::false_type{});

template <typename T>
auto test(...) -> std::true_type;

template <typename T>
using is_enum_class = std::integral_constant<bool, decltype(test<T>(0))::value && std::is_enum<T>::value>;

DEMO

Option #2:

In void_t-fashion:

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

template <typename T>
struct test<T, decltype((void)+T{})> : std::true_type {};

template <typename T>
using is_enum_class = std::integral_constant<bool, !test<T>::value && std::is_enum<T>::value>;

DEMO 2

Tests:

enum class EC { a, b };
enum E { c, d };

int main()
{
static_assert(is_enum_class<EC>::value, "!");
static_assert(!is_enum_class<E>::value, "!");
static_assert(!is_enum_class<int>::value, "!");
}

Dealing with inconsistent typedefs in generic code

Maybe can be done in a simpler way... anyway, I propose a tag dispatching / SFINAE solution.

First of all, a simple recursive tag struct

template <std::size_t N>
struct tag : public tag<N-1u>
{ };

template <>
struct tag<0u>
{ };

to avoid ambiguities in cases more that one of the possible type names are defined.

Then a template function (only declared) for every type you want extract from the possible types; one for type

template <typename T, std::void_t<typename T::type>* = nullptr>
typename T::type getType (tag<0u>);

one for this_type

template <typename T, std::void_t<typename T::this_type>* = nullptr>
typename T::this_type getType (tag<1u>);

one for ThisType

template <typename T, std::void_t<typename T::ThisType>* = nullptr>
typename T::ThisType getType (tag<2u>);

and one (to be a little silly) for MySillyTypeName

template <typename T, std::void_t<typename T::MySillyTypeName>* = nullptr>
typename T::MySillyTypeName getType (tag<3u>);

Observe that the number of the tag are differents: this avoid the possible ambiguity and give a priority order for the names.

Now a trivial struct that uses getType() to extract the required type

template <typename T, typename U = decltype(getType<T>(tag<100u>()))>
struct GetType { using type = U; };

The following is a full compiling C++17 example

#include <type_traits>

template <std::size_t N>
struct tag : public tag<N-1u>
{ };

template <>
struct tag<0u>
{ };

template <typename T, std::void_t<typename T::type>* = nullptr>
typename T::type getType (tag<0u>);

template <typename T, std::void_t<typename T::this_type>* = nullptr>
typename T::this_type getType (tag<1u>);

template <typename T, std::void_t<typename T::ThisType>* = nullptr>
typename T::ThisType getType (tag<2u>);

template <typename T, std::void_t<typename T::MySillyTypeName>* = nullptr>
typename T::MySillyTypeName getType (tag<3u>);

template <typename T, typename U = decltype(getType<T>(tag<100u>()))>
struct GetType { using type = U; };

struct foo1 { using type = short; };
struct foo2 { using this_type = int; };
struct foo3 { using ThisType = long; };
struct foo4 { using MySillyTypeName = long long; };

int main()
{
static_assert( std::is_same_v<short, GetType<foo1>::type> );
static_assert( std::is_same_v<int, GetType<foo2>::type> );
static_assert( std::is_same_v<long, GetType<foo3>::type> );
static_assert( std::is_same_v<long long, GetType<foo4>::type> );
}


Related Topics



Leave a reply



Submit