简体   繁体   中英

How do I make this template argument variadic?

Say I have a template declaration like this:

template <class A, class B, class C = A (&)(B)>

How would I make it so that I could have a variable amount of objects of type C ? Doing class C ...c = x won't work because variadic template arguments can't have default values. So this is what I've tried:

template <typename T>
struct helper;

template <typename F, typename B>
struct helper<F(B)> {
    typedef F (&type)(B);
};

template <class F, class B, typename helper<F(B)>::type ... C>
void f(C ...c) { // error

}

But up to the last part I get error messages. I don't think I'm doing this right. What am I doing wrong here?

I think you can use the following approach. First, some machinery for type traits. This allows you to determine if the types in an argument pack are homogeneous (I guess you want all functions to have the same signature):

struct null_type { };

// Declare primary template
template<typename... Ts>
struct homogeneous_type;

// Base step
template<typename T>
struct homogeneous_type<T>
{
    using type = T;
    static const bool isHomogeneous = true;
};

// Induction step
template<typename T, typename... Ts>
struct homogeneous_type<T, Ts...>
{
    // The underlying type of the tail of the parameter pack
    using type_of_remaining_parameters = typename 
        homogeneous_type<Ts...>::type;

    // True if each parameter in the pack has the same type
    static const bool isHomogeneous = 
        is_same<T, type_of_remaining_parameters>::value;

    // If isHomogeneous is "false", the underlying type is a fictitious type
    using type = typename conditional<isHomogeneous, T, null_type>::type;
};

// Meta-function to determine if a parameter pack is homogeneous
template<typename... Ts>
struct is_homogeneous_pack
{
    static const bool value = homogeneous_type<Ts...>::isHomogeneous;
};

Then, some more type traits to figure out the signature of a generic function:

template<typename T>
struct signature;

template<typename A, typename B>
struct signature<A (&)(B)>
{
    using ret_type = A;
    using arg_type = B;
};

And finally, this is how you would define your variadic function template:

template <typename... F>
void foo(F&&... f)
{
    static_assert(is_homogeneous_pack<F...>::value, "Not homogeneous!");
    using fxn_type = typename homogeneous_type<F...>::type;

    // This was template parameter A in your original code
    using ret_type = typename signature<fxn_type>::ret_type;

    // This was template parameter B in your original code
    using arg_type = typename signature<fxn_type>::arg_type;

    // ...
}

Here is a short test:

int fxn1(double) { }
int fxn2(double) { }
int fxn3(string) { }

int main()
{
    foo(fxn1, fxn2); // OK
    foo(fxn1, fxn2, fxn3); // ERROR! not homogeneous signatures
    return 0;
}

Finally, if you need an inspiration on what to do once you have that argument pack, you can check out a small library I wrote (from which part of the machinery used in this answer is taken). An easy way to call all the functions in the argument pack F... f is the following (credits to @MarkGlisse):

initializer_list<int>{(f(forward<ArgType>(arg)), 0)...};

You can easily wrap that in a macro (just see Mark's answer to the link I posted).

Here is a complete, compilable program:

#include <iostream>
#include <type_traits>

using namespace std;

struct null_type { };

// Declare primary template
template<typename... Ts>
struct homogeneous_type;

// Base step
template<typename T>
struct homogeneous_type<T>
{
    using type = T;
    static const bool isHomogeneous = true;
};

// Induction step
template<typename T, typename... Ts>
struct homogeneous_type<T, Ts...>
{
    // The underlying type of the tail of the parameter pack
    using type_of_remaining_parameters = typename
        homogeneous_type<Ts...>::type;

    // True if each parameter in the pack has the same type
    static const bool isHomogeneous =
        is_same<T, type_of_remaining_parameters>::value;

    // If isHomogeneous is "false", the underlying type is a fictitious type
    using type = typename conditional<isHomogeneous, T, null_type>::type;
};

// Meta-function to determine if a parameter pack is homogeneous
template<typename... Ts>
struct is_homogeneous_pack
{
    static const bool value = homogeneous_type<Ts...>::isHomogeneous;
};

template<typename T>
struct signature;

template<typename A, typename B>
struct signature<A (&)(B)>
{
    using ret_type = A;
    using arg_type = B;
};

template <typename F>
void foo(F&& f)
{
    cout << f(42) << endl;
}

template <typename... F>
void foo(typename homogeneous_type<F...>::type f, F&&... fs)
{
    static_assert(is_homogeneous_pack<F...>::value, "Not homogeneous!");
    using fxn_type = typename homogeneous_type<F...>::type;

    // This was template parameter A in your original code
    using ret_type = typename signature<fxn_type>::ret_type;

    // This was template parameter B in your original code
    using arg_type = typename signature<fxn_type>::arg_type;

    cout << f(42) << endl;
    foo(fs...);
}

int fxn1(double i) { return i + 1; }
int fxn2(double i) { return i * 2; }
int fxn3(double i) { return i / 2; }
int fxn4(string s) { return 0; }

int main()
{
    foo(fxn1, fxn2, fxn3); // OK

    // foo(fxn1, fxn2, fxn4); // ERROR! not homogeneous signatures

    return 0;
}
template <typename T>
struct helper;

template <typename F, typename B>
struct helper<F(B)> {
    typedef F (*type)(B);
};

template<class F, class B>
void f()
{
}

template <class F, class B, typename... C>
void f(typename helper<F(B)>::type x, C... c)
{
    std::cout << x(B(10)) << '\n';
    f<F,B>(c...);
}

int identity(int i) { return i; }
int half(int i) { return i/2; }
int square(int i) { return i * i; }
int cube(int i) { return i * i * i; }

int main()
{
    f<int,int>(identity,half,square,cube);
}

Here's a modified version that can deduce types:

template<class F, class B>
void f(F(*x)(B))
{
    x(B());
}

template <class F, class B, typename... C>
void f(F(*x)(B), C... c)
{
    f(x);
    f<F,B>(c...);
}

int identity(int i) { return i; }
int half(int i) { return i/2; }
int square(int i) { return i * i; }
int cube(int i) { return i * i * i; }
int string_to_int(std::string) { return 42; }

int main()
{
    f(identity,half,square,cube);
    // f(identity,half,string_to_int);
}

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