How do I get the type of the elements in a vector?
Asked Answered
H

5

8

Considering template metaprogramming techniques if I have

std::vector<int> v;
v.push_back(42.42f);

this works, mainly because the constructor being used it's not marked explicit, in other words my push_back it's not type safe.

Now I'm in a situation where I don't even know how a container v is declared, in this case it's int, but I need to deduce the type automatically while having a generic std::vector<T>. I would like a solution to get T.

in C++11 there is something like remove_all_extents ( with an useful member type ) but apparently it's only useful for the old arrays, but it's basically what I would like to achieve.

I would like to trigger an error when the push_back it's not type safe or deduce the type so I can write an assertion or implement something on my own.

I really can't find a working solution for this, it's so simple in theory, but once a vector is declared there is no explicit information about the type used for the elements.

I also would like to avoid explicit type inference, like translating foo from my ideal function call

foo( container, elements ... )

to

foo<int>(container, elements ...)

where int is the type for the elements of container, this is not safe too in my opinion, it's also more verbose and error prone.

So how do I retrieve the type for the elements of a container in C++11 ?

Homy answered 28/1, 2014 at 7:54 Comment(1)
Perhaps you're looking for std::vector<T>::value_type ?Araliaceous
K
16

You can get the type like this:

typename std::vector<T>::value_type;

Then use static_assert together with std::is_same.

template <typename T1, typename T2>
void special_push_back(std::vector<T1>& v, T2 elem)
{
  // check that T1 and T2 are the same before pushing elem into v
}

then

std::vector<int> v;
special_push_back(v, 3.14); // Compile time error: double is not int
Kinson answered 28/1, 2014 at 7:56 Comment(6)
nope, I have an instance of a vector, I have container which represent an already allocated container passed to the interface of foo, I have to go the other way around.Homy
@Homy I am not sure I follow. I added an example. I hope this helps.Kinson
imagine I have foo that takes a container + elements, it's a variadic template basically. notice that i used ... in my example of foo.Homy
@user2485710: if I understood that right, decltype(container)::value_type should workAraliaceous
I was trying to implement something in another way but it's probably way more cumbersome than what you showed here. I'll probably add the type of the vector to the template just like you did here and so I can simplify this.Homy
@Araliaceous thanks, another great alternative, I will try that.Homy
G
10

If you have C++11 features, you can also use decltype keyword to ease the access to underlying value_type type member:

decltype(TheContainer)::value_type nVarOfType;

Here, TheContainer might be a container of any type. For instance, a map<int, string>, deque<float> or any other STL container - all STL containers have value_type type-defined. decltype key would give the type of given object.

Greaseball answered 28/1, 2014 at 8:36 Comment(0)
T
2

If I understood the comments correctly, you may also try:

template <class Container, class... Args>
void foo(Container&& c, Args&&... args) {
    typedef typename Container::value_type value_type;

    // use type_traits to check value_type
    ...
}

An improvement would be to check whether Container has an embedded type value_type at all:

template <class T>
struct has_value_type
{
private:
    template <class U> static std::false_type test(...);
    template <class U> static std::true_type test(typename U::value_type*);
public:
    enum { value = decltype(test<T>(0))::value };
};

and then use std::enable_if:

template <class Container, class... Args>
typename std::enable_if<has_value_type<Container>::value, return_type>::type 
foo(Container&& c, Args&&... args) {
    typedef typename Container::value_type value_type;
    ...
}

Similarly, you can "enable" only templates which fulfill your requirement of the value type:

template <class Container, class... Args>
typename std::enable_if<
    has_value_type<Container>::value
    and std::is_same<int, typename Container::value_type>::value
>::type
foo2(Container&& c, Args&&... args) {
    typedef typename Container::value_type value_type;

    // here value_type equals int
}

Edit:

With even more "template tricks", you can for example ensure that all types of the variadic template pack are equal and and equals the value_type of the container:

First, a helper template:

template <typename...>
struct all_of;

template <typename T>
struct all_of<T> : std::conditional<T::value == true,
    std::true_type, std::false_type>::type
{};

template <typename Head, typename... Tail>
struct all_of<Head, Tail...> : std::conditional<
    Head::value == true and all_of<Tail...>::value,
    std::true_type,
    std::false_type>::type
{};

Without any further code, you can use it already, for example as follows:

`all_of<std::is_nothrow_copy_constructible<Args>...>::value`

While the syntax may appear weird, the const expression above evaluates to true if all types in the parameter pack Args have the property that its copy-constructor will not throw.

Given the helper class template all_of, we can now enable the function template foo only if all types in the parameter pack equal the value_type of the container:

template <class Container, class... Args>
typename std::enable_if<
    has_value_type<Container>::value
    and all_of<std::is_same<typename Container::value_type, Args>...>::value
>::type
foo2(Container&& c, Args&&... args) {
    typedef typename Container::value_type value_type;

    // here *all* types in Args are equal value_type
}
Talc answered 28/1, 2014 at 8:20 Comment(0)
C
1

I see that the question has been there for some time, but for a sake of completeness - another

approach, that doesn't depend on if a vector is empty.

Major idea is to get a raw pointer and to decay its type. Obviously is going to work with C-style arrays as well, besides that this works pretty much with any data type.

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

template <class T>
using decay_all_t = 
typename std::remove_cvref<typename std::remove_pointer<T>::type>::type;

struct MyStruct {...};

int main {
    std::vector<MyStruct> j {...};

    auto *_ = j.data();
    using type = decay_all_t<decltype(_)>;

    std::cerr << typeid(type).name() << '\n';
}
Choreography answered 6/10, 2021 at 19:26 Comment(0)
B
1

You may want to consider using std::ranges::range_value_t<container name> as an alternative to using directly container type::value_type.

Bestead answered 6/3, 2023 at 16:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.