Search code examples
c++c++17variadic-templatestemplate-meta-programmingsfinae

Create foldable template parameter pack


Question

Is it possible to create foldable (※ fold expression) template parameter pack?

Example

Consider the following example (function that takes two arguments of type int (decayed)).

template<
    typename L,
    typename R,
    typename = std::enable_if_t<
            std::is_same_v<int, std::decay_t<L>>
        &&  std::is_same_v<int, std::decay_t<R>>
    >
>
int F(L Left, R Right){
    return 0x70D0;
}

Is it possible to create template parameter pack that can be folded to avoid writing same fragment of code multiple times (ie std::is_same_v)?

Something that represented as std::pack below could simplify using SFINAE?

typename = std::enable_if_t<(... && std::is_same_v<int, std::decay_t<std::pack<L, R>>>)>

What I've tried

I've tried to solve the problem using T pack and aliasing single L and R. But for some reaon the following code compiles and runs without an error (second argument of second F function call, decayed, does not equal int) on MSVC 15.9.4+28307.222:

template<
    typename... T,
    typename L = std::tuple_element_t<0, std::tuple<T...>>,
    typename R = std::tuple_element_t<1, std::tuple<T...>>,
    typename = std::enable_if_t<(... && std::is_same_v<int, std::decay_t<T>>)>
>
int F(L Left, R Right){
    return 0x70D0;
}

int main(){
    F(3, 5);   // OK
    F(3, "5"); // OK, but should not compile
}

PS Also, did I miss something in the above code to make SFINAE work properly (filter functions with int, int (decayed) arguments only)?


Solution

  • Is it possible to create template parameter pack that can be folded to avoid writing same fragment of code multiple times?

    In place? Not in C++17. You would have to wrap your types into some sort of template <typename...> struct typelist; and then unwrap them somewhere else. This requires one layer of indirection.

    There's no way to write anything like std::pack as far as I know.


    I've tried to solve the problem using T pack and aliasing single L and R. [...]

    In your code, T... will always be empty as it's not being deduced by anything. L and R's default template parameter values are ignored, as they are being deduced by the function call.

    You need something like:

    template<
        typename... T,
        typename = std::enable_if_t<(... && std::is_same_v<int, T>)>
    >
    int F(T...){
        return 0x70D0;
    }
    

    In C++20, you should be able to use a lambda as follows:

    template<
        typename L,
        typename R,
        typename = std::enable_if_t<[]<typename... Ts>(){
            return (... && std::is_same_v<int, Ts>)
        }.operator()<L, R>()>
    >
    int F(L Left, R Right){
        return 0x70D0;
    }