Sc2slash
Sc2slash

Reputation: 13

C++ Parameter Pack type expansion

I was playing around with parameter packs in c++ and here's what I would like to achieve:

I have this 3 structs:

struct Base {
    Base(int x) : x(41) {}
    int x;
};

struct A : Base {
    A() : Base(41) {}
};

struct B : Base {
    B() : Base(42) {}
};

And I would like to create a generic function that returns a tuple with instances of the types specified, i.e.

magicFunction<A,B,A,B,A> == std::tuple{A(), B(), A(), B(), A()} 
magicFunction<A,B,A> == std::tuple{A(), B(), A()} 

I tried to use template specialization to expand the types but was unfortunately unable to do it (I understand why it doesn't compile, just don't know how to fix it :) )


template<typename isEmpty, typename ...T>
struct ExpanderImpl {

};

template<typename Head, typename ... Rest>
struct ExpanderImpl<std::false_type, Head, Rest...> {
    static std::tuple<Head, Rest...> getValues() {
        if(sizeof...(Rest) > 0) {
            return std::tuple_cat(std::tuple{Head()}, ExpanderImpl<std::false_type, Rest...>::getValues());
        } else {
            return std::tuple_cat(std::tuple{Head()}, ExpanderImpl<std::true_type, Rest...>::getValues());
        }
    }
};

template<typename ...Empty>
struct ExpanderImpl<std::true_type, Empty...> {
    static std::tuple<Empty...> getValues() {
        return {};
    }
};

template<typename ...T>
struct Expander {
    static std::tuple<T...> getValues() {
        if(sizeof...(T) > 0) {
            return ExpanderImpl<std::false_type, T...>::getValues();
        } else {
            return ExpanderImpl<std::true_type, T...>::getValues();
        }
    }

};

Any suggestions on how to fix it? Also, is there a better way of achieving what I want?

The full code can be found here here . Thanks for the help.

Upvotes: 1

Views: 444

Answers (3)

Jarod42
Jarod42

Reputation: 217085

  • Whereas there are simpler ways to create tuple, your way fails as both branches should be instantiated with regular if.

    if constexpr (C++17) solves that Demo.

    Notice that std::tuple{Head()} is also C++17 construct.

    C++11 would be std::tuple<Head>{} or std::make_tuple(Head()).

  • Way to keep your implementation in C++11 would be to get rid of this runtime if, and use std::conditional to use either std::true_type/std::false_type

    template<typename Head, typename ... Rest>
    struct ExpanderImpl<std::false_type, Head, Rest...> {
        static std::tuple<Head, Rest...> getValues() {
            return std::tuple_cat(
                std::make_tuple(Head()),
                ExpanderImpl<typename std::conditional<(sizeof...(Rest) == 0),
                                                       std::true_type,
                                                       std::false_type>::type,
                             Rest...>::getValues());
        }
    };
    
    template<typename ...T>
    struct Expander {
        static std::tuple<T...> getValues() {
            return ExpanderImpl<typename std::conditional<(sizeof...(T) == 0),
                                                          std::true_type,
                                                          std::false_type>::type,
                                T...>::getValues();
        }
    };
    

    Demo.

  • And you can get rid of is_empty wth different specialization:

    template<typename ...Ts> struct Expander;
    
    // Non empty case
    template<typename Head, typename ... Rest>
    struct Expander<Head, Rest...> {
        static std::tuple<Head, Rest...> getValues() {
            return std::tuple_cat(
                std::make_tuple(Head()),
                Expander<Rest...>::getValues());
        }
    };
    
    // Empty case
    template<>
    struct Expander<> {
        static std::tuple<> getValues() { return {}; }
    };
    

    Demo.

Upvotes: 0

3CxEZiVlQ
3CxEZiVlQ

Reputation: 38341

You seem to be looking for

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

int main() {
    magicFunction<A,B,A>();

    // If no default constructors supplied.
    magicFunction<Base,Base,Base>(41, 42, 41); 
}

Upvotes: 1

NathanOliver
NathanOliver

Reputation: 180415

What you are looking for is

template <typename... T> std::tuple<T...> magicFunction() 
{
    return {};
}

and you would call it like

magicFunction<A,B,A,B,A>();

And it will return a std::tuple<A,B,A,B,A>. The way this works is return {}; says return a value initialized (T()/T{}) return value, and the return value is std::tuple<T...> which is a tuple of all the template parameters

Upvotes: 1

Related Questions