Type alias a template parameter pack

c++ templates variadic-templates template-meta-programming

638 观看

4回复

176 作者的声誉

In the sample below I'm trying to alias a template parameter pack basically.

It's not possible in the standard so I found people work around the limitation with a tuple or empty templated struct. However my situation seems different because I don't have an argument parameter type pack to match...

(I know this sample seems stupid but it's a minimal PoC. In the codebase the typelist gets a lot longer and this would really be useful.)

#include <iostream>
#include <tuple>

template<typename T>
void f(T t)
{
    std::cout << "templated f()" << std::endl;

}
template<>
void f(int t)
{
    std::cout << "specialized f()" << std::endl;
}

template<typename A, typename B>
void fn()
{
    f(A());
}

template<int, char>
void fn()
{
    int a;
    f(a);
}

// Can't seem to define this correctly.
//using MyType = std::tuple<int, char>()::type;

int
main(int, char**)
{
    fn<char, char>();

    // I have
    fn<int, char>();

    // I want some alias;
    //fn<MyType>();

    return 0;
}

references;

作者: dzan 的来源 发布者: 2017 年 12 月 27 日

回应 4


1

7904 作者的声誉

A parameter pack has to be just that, a list of several types. It is possible to forward a parameter pack (once you have one) by using the ... operator on it thus re-expanding it back into a list, but you have to have those types to begin with.

And note that: using MyType = std::tuple()::type; isn't going to work because tuple doesn't have a 'type' member. You can't even do (with or without the ...): template struct holder { typedef Types... type; }; using my_type = holder::type;

作者: SoronelHaetir 发布者: 2017 年 12 月 27 日

1

18576 作者的声誉

In the sample below I'm trying to alias a template parameter pack basically.

It's not possible in the standard so I found people work around the limitation with a tuple or empty templated struct.

std::tuple<...> is great for doing for aliasing parameter packs!

However my situation seems different because I don't have an argument parameter type pack to match...

Well, these a cases where you find out that class-templates are actually more powerful than function-templates - in this case, that we can have partial template-specializations; So if you decide to use a class template with an overloaded operator (), I did say, its possible:

Demo:

template<typename... A>
struct fn
{
    void operator () () const 
    { std::cout << "primary f()" << std::endl; }
};

template<typename... A>
struct fn<std::tuple<A...>>
{
    void operator () () const 
    { std::cout << "specialized on unlimited params()" << std::endl; }
};

template<>
struct fn<std::tuple<char, double*>>
{
    void operator () () const 
    { std::cout << "specialized on f<char, double*>()" << std::endl; }
};

template<typename A, typename B>
struct fn<std::tuple<A, B>>
{
    void operator () () const
    { std::cout << "specialized on two params f()" << std::endl; }
};

// Can't seem to define this correctly.
using MyType = std::tuple<int, char>;
using MyType2 = std::tuple<char, double*>;
using MyType3 = std::tuple<int, char, double, void*>;

int main()
{
    fn<int, char>()();
    fn<MyType>()();
    fn<MyType2>()();
    fn<MyType3>()();
    return 0;
}

Output:

primary f()
specialized on two params f()
specialized on f<char, double*>()
specialized on unlimited params()
作者: WhiZTiM 发布者: 2017 年 12 月 27 日

0

5046 作者的声誉

Since C++14, if you don't want to overload or rewrite your function, nor you want to write a functor class template, you can use a generic lambda to locally turn a type-list into a parameter-pack:

template<typename T>
struct quote{ using type = T; };

template<typename T>
using unpack = typename T::type;

template<typename... T>
using params = std::tuple<quote<T>...>;

// to be used as

template<typename A, typename B>
void foo();

using my_params = params<int,char>;

int main()
{
  std::apply( []( auto... params ){ foo< unpack<decltype(params)>... >(); }, my_params{} );
}

PS: std::apply requires C++17, but can be implemented in >=C++11 as well...

PPS: in C++20, we may even write []<typename... T>(){ foo<T...>(); } making the solution even cleaner ...

作者: Massimiliano Janes 发布者: 2017 年 12 月 27 日

2

36 作者的声誉

决定

If we use a templated empty struct to alias the type pack, it is possible to use SFINAE on your original function so that when it is invoked for our special struct it simply forwards the encapsulated type pack to the original implementation.

Here is how that would look in C++14:

template <typename... Types>
struct TypePack {};

// Helper meta functions to determine
// if the template param is a TypePack struct.
template <typename... Ts>
static constexpr bool is_typepack_v = false;

template <typename... Ts>
static constexpr bool is_typepack_v<TypePack<Ts...>> = true;

template <typename... Ts>
// Enabled whenever template parameter is not a TypePack.
typename std::enable_if<not is_typepack_v<Ts...>>::type 
fn() {  // FN_A
    std::cout << "Generic\n";
}

template <>
// Complete specialization, selected over generic fn for <char,int>
void fn<char,int> () {  // FN_B
    std::cout << "Specialized for char,int\n";
}

template <typename T>
// Enabled when template param is a TypePack.
typename std::enable_if<is_typepack_v<T>>::type
fn() { // FN_C
   forward_fn(T{}); 
}

// forward_fn() extracts the types from a TypePack argument
// and invokes fn with it.
template <typename ...T>
void forward_fn(TypePack<T...> /*unused*/) {
    std::cout << "Forwarding a pack alias\n";
    fn<T...>();
}

// To alias a type pack use TypePack<T1,T2..,Tn>
using CharIntPack = TypePack<char,int>;

int main() {
    fn<char,char>(); // invokes FN_A
    fn<char,int>(); //  invokes FN_B
    fn<CharIntPack>(); // invokes FN_C which then invokes FN_B
    return 0;
}

This produces the following output:

Generic
Specialized for char,int
Forwarding a pack alias
Specialized for char,int

What I like about this method is that this "trick" is required only once when defining the functions, and the user can be completely ignorant of it.

作者: shubhdev 发布者: 2017 年 12 月 28 日
32x32