简体   繁体   中英

How to represent a variadic templated type based on another group of variadic template arguments in modern C++?

Suppose I have a following variadic template structure:

template <class... T> 
struct Example {};

Now I want to define a template function:

template<class... S>
??? f() {
    return Example<???>
}

where the specialization of Example<> is depend on the template parameter S of f .

To be more concrete (and simple), now I just want to return Example<int, ...,int> , where the number of int is the size of the parameter pack S .

How can it be done in modern C++, ie C++11/14/17?

More generally, is there a way to at compile time define a function on template parameters?

You can create a mapping type:

// Maps some type T to the type U.
template <typename T, typename U>
using Map = U;

which you can use as follows:

template<class... S>
Example<Map<S, int>...> f() {
    return Example<Map<S, int>...>{};
}

When doing template meta programming, IMO it always helps to keep the "meta part" and the "non meta part" separate for as long as possible. That way you can first think about the "meta part" as if it were a normal program, operating on types instead of values. So a template becomes a function, taking some type(s) (or for "higher order programming": other templates) as input and returns some type (or for "higher order programming": other templates).

So, first, step back for a second and don't think about templates, metaprogramming, and such. You have a list S . For each item of S , you want to call some function, and assemble a list of the returned items. So you need a function that, given one item of the list, returns the item it's mapped to. Let's call this function mapping . You also need a function which takes said function and applies it to your list, ie calls the mapping on each item and assembles the result list. Let's call that map .

Now turn this into a meta program:

// mapping :: TYPE -> TYPE
// ---------------------------------------------------------
// ?? --> int (default "value")
template<typename X> struct mapping {
  using type = int;
};
// if instead you want it to be undefined for unknown types:
//template<typename X> struct mapping;
// bool --> double
template<> struct mapping<bool> {
  using type = double;
};

Now map , generalized such that it can use anything like mapping :

// map :: ([T] -> T) -> (T -> T) -> ([T] -> T)
//         "List"       "Mapping"   result "type" (also a "List")
// --------------------------------------------------------
template<template<typename...> class List,
         template<typename> class Mapping>
struct map {
  template<typename... Elements>
  using type = List<typename Mapping<Elements>::type...>;
};

Finally, apply to your Example (which is kind of a list, because it "holds" multiple types) and the concrete mapping :

template<typename... S>
using MappedExample = map<Example, mapping>::type<S...>;

Now you've got the resulting template, use it in your non-meta program:

template<typename... S>
MappedExample<S...> f() {
  return MappedExample<S...>{};
}

Live example :

int main() {
  std::cout
    << typeid(Example<bool,int,char,double>).name()
    << std::endl
    << typeid(decltype(f<bool, int, char, double>())).name()
    << std::endl;
}

Output:

7ExampleIJbicdEE in the first line, means an Example with template parameters b ool, i nt, c har, d ouble.
7ExampleIJdiiiEE as second line, means an Example with template parameters d ouble (mapped from the bool) and 3 i nt (the default mapping).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM