metablaster
metablaster

Reputation: 2184

Using type traits on template parameter packs?

In bellow sample code I'm trying to check if function arguments are pointers or not with std::is_pointer

it works fine if there is only one parameter, but how to make it work with more parameters, such as in parameter pack?

#include <type_traits>
#include <iostream>

class Test
{
public:
    template<typename... Params>
    void f(Params... params);

    template<typename T, typename... Params>
    auto sum(T arg, Params... params)
    {
        return arg + sum(params...);
    }

    template<typename T>
    auto sum(T arg)
    {
        return arg;
    }

    int member = 1;
};

template<typename... Params>
void Test::f(Params... params)
{
    // ERROR: too many template arguments for std::is_pointer
    if constexpr (std::is_pointer_v<Params...>)
        member += sum(*params...);
    else
        member += sum(params...);

    std::cout << member;
}

int main()
{
    Test ts;

    // both fail
    ts.f(1, 2);
    ts.f(&ts.member, &ts.member);

    // is that even possible?
    ts.f(&ts.member, 2);

    return 0;
}

I guess if parameters are not either all pointers or all not pointers then we have additional issue, but let's just assume all arguments are either pointers or not.

then what about if arguments are mix of pointers and non pointers anyway?

Upvotes: 1

Views: 1136

Answers (3)

Richard Hodges
Richard Hodges

Reputation: 69892

The problem can be simplified (and the program made to work) by moving the detection of whether a param is a pointer into the variadic template function sum.

example:

#include <type_traits>
#include <iostream>

class Test
{
public:
    template<typename... Params>
    void f(Params... params)
    {
        member += sum(params...);

        std::cout << member << '\n';
    }

    template<typename... Params>
    auto sum(Params... params)
    {
        auto contents = [](auto param)
        {
            using ParamType = std::decay_t<decltype(param)>;
            if constexpr (std::is_pointer_v<ParamType>)
                return *param;
            else
                return param;
        };
        return (contents(params) + ...);
    }

    int member = 1;
};

int main()
{
    Test ts;

    // both fail
    ts.f(1, 2);
    ts.f(&ts.member, &ts.member);

    // is that even possible?
    ts.f(&ts.member, 2);

    return 0;
}

Expected output:

4
12
26

https://godbolt.org/z/y57-TA

Upvotes: 1

Douglas Oliveira
Douglas Oliveira

Reputation: 155

For anyone using C++11/14, you can use the following solution.

// helper struct that checks the list
template<int N, typename... Args>
struct is_all_pointer_helper;
// N > 1, checks the head and recursively checks the tail
template<int N, typename Arg, typename... Args>
struct is_all_pointer_helper<N, Arg, Args...> {
    static constexpr bool value = 
            std::is_pointer<Arg>::value && 
            is_all_pointer_helper<N-1, Args...>::value;
};
// N == 1, checks the head (end of recursion)
template<typename Arg, typename... Args>
struct is_all_pointer_helper<1, Arg, Args...> {
    static constexpr bool value = std::is_pointer<Arg>::value;
};
// N == 0, define your result for the empty list
template<> struct is_all_pointer_helper<0> {
    static constexpr bool value = false;
};

// user interface
template<typename... Args>
struct is_all_pointer : is_all_pointer_helper<sizeof...(Args), Args...> {};
// C++14 only
template<typename... Args>
constexpr bool is_all_pointer_v = is_all_pointer<Args...>::value;

class Foo {};

int main()
{
    cout << std::boolalpha << is_all_pointer<int*, char*, Foo*>::value << endl;
    cout << std::boolalpha << is_all_pointer_v<int*, char, Foo*> << endl; //C++14
    cout << std::boolalpha << is_all_pointer<>::value << endl;
}

Output:

true
false
false

Upvotes: 2

Fureeish
Fureeish

Reputation: 13424

You can use a fold expression:

#include <iostream>
#include <type_traits>

template <typename... Ts>
void test(Ts... ts) {
    if constexpr ((std::is_pointer_v<Ts> && ...)) {
        std::cout << "yes\n";
    } else {
        std::cout << "no\n";
    }
}

int main() {
    test(new int, new char, new int);
    test(new int, new char, new int, 2);
}

The output of the program:

yes
no

Be careful with your function template signature though - I would advise using Ts&&... ts instead of Ts... ts, because of how const char[]s are treated. With my original example, test(new int, new char, new int, "hello"); will yield a yes output, but with Ts&&... ts, it will yield no.

Upvotes: 6

Related Questions