6

(This question has an answer by Nim which mentions boost::mpl::map.)

Is there a compile-time container in standard C++ which can hold types?

A usage example would be:

compiler::vector foo{char, short, long, long long};

template <int N>
void bar(foo[N] param){/*do something and return foo[N]*/}
Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 1
    MPL has it as [`boost::mpl::vector`](http://www.boost.org/doc/libs/1_57_0/libs/mpl/doc/refmanual/vector.html). – Joseph Mansfield Feb 27 '15 at 16:34
  • Without context I am not sure it will help you, but I think variadic template arguments can be a way to solve the problem, since a sequence of template parameters can be thought of as a type container. – Bérenger Feb 27 '15 at 16:37
  • 1
    This isn't really an answer, but when it comes to typelisting, I typically head to Loki (http://loki-lib.sourceforge.net/html/a00681.html). Boost was already mentioned, but in cases where you can't (or won't) use boost, Loki has a lot of solutions. – kwierman Feb 27 '15 at 16:40
  • @Bérenger So I guess you're varadic template idea would be similar to [Dan's answer to the question](http://stackoverflow.com/a/28699568/2642059)? – Jonathan Mee Feb 27 '15 at 17:28
  • 1
    I think the `do something and return foo[T]` is relevent, because there's a implied level of compile-time code there won't work. Namely, one function can't return multiple types. However, you can specialize or delegate to overloads, which works fine. – Mooing Duck Feb 27 '15 at 17:57
  • @MooingDuck A good point I'm not thinking, I'll change it to the parameter so it's like `template void foo(T bar){};` – Jonathan Mee Feb 27 '15 at 18:47
  • It's rather confusing to call a number `T`. That usually stands for "type". Can't you call it `N` instead? – Lightness Races in Orbit Feb 28 '15 at 18:43
  • @MooingDuck After rereading your comment I'm not sure that I fully understood it. I was seeking clarification and posted a questino here: http://stackoverflow.com/q/28906771/2642059 – Jonathan Mee Mar 06 '15 at 20:09

3 Answers3

3

In c++11 you can use std::tuple : (disclaimer: not tested)

#include <tuple>
#include <type_traits>
std::tuple<char, short, long, long long> foo;

// reference type
template <int N>
void bar(decltype(std::get<N>(foo)) param){...}


// value type
template <int N>
void bar(std::remove_reference<decltype(std::get<N>(foo))>::type param)

Note that this is not completely what you want since you will either have only value or reference types, even if both are mixed in the tuple declaration of foo.

The value of the tuple are never used. I think with compiler optimization, foo will actually never be instanciated in the object code

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
Bérenger
  • 2,678
  • 2
  • 21
  • 42
  • 2
    You don't even need `foo`, just `decltype` and `declval`. – Lightness Races in Orbit Feb 27 '15 at 17:53
  • 2
    I think the `decltype(std::get(foo))` will result in an unintentional reference being added to the type at index `T`, because `std::get` will return a reference type. – Julian Feb 27 '15 at 18:19
  • @Julian: No, that's not [how `decltype` works](http://en.cppreference.com/w/cpp/language/decltype). – Lightness Races in Orbit Feb 27 '15 at 18:27
  • 1
    @LightnessRacesinOrbit: If you check the return type of `std::get` e.g. [here](http://en.cppreference.com/w/cpp/utility/tuple/get) you'll see that it returns never the plain type but always an rvalue or lvalue reference. At least for me, the following assertion gives no error: `static_assert(std::is_same(std::declval>()))>::value,"error")` – Julian Feb 27 '15 at 18:40
  • @Julian Correct, I updated the answer to take your comment into account – Bérenger Feb 27 '15 at 19:35
  • @Bérenger Seems to test well, please forgive the obfuscated example: http://ideone.com/e.js/EXsM9o Also I recommend you use `remover_reference_t` if you have C++14 at your disposal. – Jonathan Mee Feb 27 '15 at 19:42
  • @Julian: Yes, I know what `std::get` does but, as I said, you're forgetting what `decltype` does. Follow the link I provided. Rvalue expressions naming lvalue references get the reference type dropped. Your example with `declval` is not the same as the prior example. – Lightness Races in Orbit Feb 28 '15 at 06:15
  • @LightnessRacesinOrbit: If you were right, then I shouldn't be able to do [this](http://ideone.com/X9MEC3), or should I (I am using the code from the original answer)? It sure looks like I can modify the contents of `foo` by assigning a value to `bar<1>()` which obviously means that there is a reference being added to the type... or that there is a compiler bug (I don't know the standard that well). – Julian Feb 28 '15 at 08:28
  • @LightnessRacesinOrbit In his previous example, `foo` is an lvalue, and the result of `std::get(foo)` has lvalue reference type and is also an lvalue. What rvalue are you referring to? `decltype` will yield an lvalue reference type in that case. Also, would you mind giving an example of what you mean by "Rvalue expressions naming lvalue references"? You got me wondering there... – bogdan Feb 28 '15 at 09:00
  • @LightnessRacesinOrbit: I would also be interested in what exactly you mean by the "Rvalues expressions naming lvalue references"... I feel like there is some aspect of `decltype` that I don't understand quite yet. – Julian Feb 28 '15 at 18:34
  • @Julian: That example confuses a few things. Fundamentally, it has nothing to do with references. It works in a similar way to how `*obj=123` works: dereference gives you an lvalue `T`, not an rvalue `T&`. But it's also only an _indirect_ example of what we're talking about since you've introduced a new function call. – Lightness Races in Orbit Feb 28 '15 at 18:38
  • @bogdan: I linked to the documentation that shows otherwise. `decltype` can be confusing and intuitive but it is not ill-specified or ambiguous. _"decltype will yield an lvalue reference type in that case"_ is simply wrong. Surely, the expression `std::get(foo)` is an rvalue with type "[lvalue-]reference to `T`", no? Then `decltype` yields `T` in that case. – Lightness Races in Orbit Feb 28 '15 at 18:38
  • @bogdan: Hmm, no, expressions with reference type are lvalue (or xvalue) expressions (per `[C++11: 5/5]`). Alright then :) – Lightness Races in Orbit Feb 28 '15 at 18:45
3

As a type container, the standard provides you with std::tuple and -- as bogdan commented -- you can access the type elements using std::tuple_element.

using foo = std::tuple<char, short&, const long&&, long long>;

template <int N>
typename std::tuple_element<N,foo>::type bar(){/*...*/}

Even if std::tuple_element did not exist, you could easily build your own:

/// We want a metafunction to accept an index N into our type list LIST
template <unsigned N, typename LIST> struct
tuple_element;

/// Specialization for the case where N==0
template <template <typename ...> class LIST_T,typename T,typename...ELMS> struct
tuple_element<0,LIST_T<T,ELMS...>> {
    using type = T; // just take the first type from the list
};

template <unsigned N, template <typename ...> class LIST_T,typename T,typename...ELMS> struct
tuple_element<N,LIST_T<T,ELMS...>> {
    /// fallback for N>0: delegate the result type recursively until N->0
    using type = typename tuple_element<N-1,LIST_T<ELMS...>>::type;
};

// create a convenience wrapper for the template
template <unsigned N, typename LIST> using
type_at = typename tuple_element<N, LIST>::type;

Now you can define your type list, e.g. like so:

using foo = std::tuple<char, short&, const long&&, long long>;

And you can easily access it's elements using type_at<N, foo>:

static_assert(std::is_same< type_at<0,foo>, char>::value,"error");
static_assert(std::is_same< type_at<1,foo>, short&>::value,"error");
static_assert(std::is_same< type_at<2,foo>, const long&&>::value,"error");
static_assert(std::is_same< type_at<3,foo>, long long>::value,"error");

template <int N>
type_at<N,foo> bar(){/*...*/}
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
Julian
  • 1,472
  • 1
  • 11
  • 19
  • 1
    Doesn't [`std::tuple_element`](http://en.cppreference.com/w/cpp/utility/tuple/tuple_element) qualify as a 'type accessor function'? – bogdan Feb 27 '15 at 18:55
  • @bogdan: Nice one, I didn't know that one. I will adjust the answer accordingly. – Julian Feb 27 '15 at 18:59
0

This build's on Bérenger's answer, which put me onto the tuple concept. But I believe we can do better, even preserving references:

tuple foo<char&, short&, long&, long long&>;

template <int N>
void bar(tuple_element_t<N, decltype(foo)> param){}

In fact if there are no plans to use foo beyond this function we can even declare it inline:

template <int N>
void bar(tuple_element_t<N, tuple<char&, short&, long&, long long&>> param){}
Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • @Downvoter Can I get some clarification on the reason for the downvote? For me this was certainly a great solution. – Jonathan Mee Jun 03 '15 at 10:31