Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type trait to get element type of std::array or C-style array

I would like a type trait to get the element type of either a std::array or a plain old C-style array, eg. it should return char when provided with either std::array<char, 3> or char[3].

The mechanisms to do this appear to be only partially in place... I can use ::value_type on the std::array, and std::remove_all_extents on the plain array, but I can't find a single type trait that combines both and I'm unable to write one myself.

I've got as far as this:

#include <array>
#include <type_traits>

template <class T>
using element_type =  typename std::conditional<
    std::is_array<T>::value,
    typename std::remove_all_extents<T>::type,
    typename T::value_type
>::type;

It works just fine for std::array, of course:

int main()
{
    static_assert(
        std::is_same<char, element_type<std::array<char, 3>>>::value,
        "element_type failed");
}

but breaks when I pass it a plain array, because obviously plain arrays don't have a ::value_type.

static_assert(std::is_same<char, element_type<char[3]>>::value, "element_type failed");

just gives errors like "'T': must be a class or namespace when followed by '::'", as you'd expect.

If I were writing a function, I'd use std::enable_if to hide the offending template instantiation, but I don't see how this approach can be used in a type trait.

What is the correct way to solve this problem?

like image 445
Rook Avatar asked Jun 13 '17 12:06

Rook


People also ask

What is c-style array?

A C-Style array is just a "naked" array - that is, an array that's not wrapped in a class, like this: char[] array = {'a', 'b', 'c', '\0'}; Or a pointer if you use it as an array: Thing* t = new Thing[size]; t[someindex]. dosomething();

Can c-style arrays be used with stl algorithms?

So - yes, a pointer can very well be used with STL algorithms.

Do c-style arrays have methods?

c-style array as method parameter with size information Of course, the template concept can also be used for a c-style array. Instead of passing a pointer to the first element, you can pass the array as a reference to template function. This will allow to pass the array with its specific size.

What is type trait in C++?

C++ Type Traits Standard type traits The type_traits header contains a set of template classes and helpers to transform and check properties of types at compile-time. These traits are typically used in templates to check for user errors, support generic programming, and allow for optimizations.


3 Answers

For a very generic solution which supports any type of container/array supported by std::begin:

template<typename T>
using element_type_t = std::remove_reference_t<decltype(*std::begin(std::declval<T&>()))>;

std::begin as you may know returns an iterator. Dereferencing it gives you the value of it, which you can get the type of using decltype. The std::remove_reference_t is necessary because iterators return references to the element they are pointing at. As a result, this works for every single type for which std::begin has an overload.

like image 52
Rakete1111 Avatar answered Oct 02 '22 11:10

Rakete1111


What are the functions/operators you can call on both std::array and C-style arrays? operator[] of course:

template <class Array>
using array_value_type = decay_t<decltype(std::declval<Array&>()[0])>;

This will work for anything that supports looking up by integer, including std::vector, std::map<std::size_t, T>, etc.

If you want to distinguish between what you get from a const array vs a non-cost array you might want to create 2 type traits named something along the lines of:

template <class Array>
using array_element_t = decay_t<decltype(std::declval<Array>()[0])>;

template <class Array>
using array_value_t   = remove_reference_t<decltype(std::declval<Array>()[0])>;

The second trait here preserves the constness of the Array type passed in while the first one strips it. There are certainly use cases for both of these.

like image 40
SirGuy Avatar answered Oct 02 '22 11:10

SirGuy


A way to do this is to dispatch to specialized templates

template<typename>
struct arr_trait;

template<typename T, size_t N>
struct arr_trait<T[N]> {using type = T;};

template<typename T, size_t N>
struct arr_trait<std::array<T, N>> {using type = T;};

template<typename T>
struct arr_trait<T&> : arr_trait<T> {};

template<typename T>
struct arr_trait<T&&> : arr_trait<T> {};

template<typename T>
using element_type = typename arr_trait<T>::type;

Live

The reason std::conditional is failing is because it doesn't support (and neither can it as far as I know) support short-circuiting, and both types will be evaluated.

like image 23
Passer By Avatar answered Oct 02 '22 11:10

Passer By