dependent types without helper type

Viewed 70

Given typename T and int N, the templated value below generates a null function pointer of the type:

int (*) (T_0, ..., T_N)

While the code works, I don't like that it pollutes the namespace with the Temp bootstrap helper - Temp is required because types can't be enclosed in parentheses. For example, none of the following are valid.

(int (*)((int))
((int (*)(int)))
(int (*)( (I,T)... ))

The last entry shows how I would like to expand T into a list of N Ts - which of course isn't valid. It's a trick to make T depend on I but with a value only of T, thanks to the comma operator.

As a workaround, I'm forced to create the one-shot type Temp templated to make T depend on int, or in this case, I. Its usage as Temp<T,I> is valid because it doesn't enclose types in parenthesis.

However, like I said, I want to get rid of Temp because it pollutes the namespace. In the code below I restate the problem and demonstrate some attempted workarounds which sadly, all fail. For the record, I think an equivalence between template <typename T, int> using Temp = T; and template <... template<typename T1, int N1> typename Temp=T> should be allowed.

Followup: When I original published this question, I didn't know exactly why extra parentheses were disallowed, and I'm still not sure why some of my attempts failed. For example:

decltype(w<T,N>())...
result_of<w<T,N>()>::type...

I don't see any parentheses around types!


#include <iostream>
#include <typeinfo>


// Problem, #1
// This is a one-shot helper than pollutes the namespace
template <typename T, int>
using Temp = T;

// Idea #1
//  Make the one-shot helper actuall useful, where ... represents either
//  type or non-type parameters.
// Result
//  Not possible.
//template <typename T, ...>
//using Dependent = T;

// Idea #2
//  Make the types dependent within the template declaration
// Result
//  Probably not possible
//template <typename T, int N, template<typename T1, int N1> typename F=T>

// Idea #6
//  Replace the lambda with a struct (not shown)
// Result
//  Crashes gcc
template <typename T, int N>
auto a =
    []<size_t... I>
    (std::index_sequence<I...>) { 
        // Problem #2
        //  Function type declaration won't parse with extra parentheses
        //return (int (*)( (I,T)... ))nullptr;

        // Idea #3
        //  Move the templated helper into the function
        // Result
        //  Not possible
        //template <typename T, int>
        //using Temp = T;

        // Idea #4
        //  Replace the templated helper with a templated lambda which *is*
        //  allowed inside functions.
        // Result
        //  Still requires parentheses, still breaks function type declaration
        //auto w = []<typename T1, int N1>() -> T1 {};
        //return (int (*)( decltype(w<T,N>())... ));

        // Idea #5
        //  result_of (which is a template) instead of decltype
        // Result
        //  Doesn't work even without parentheses, not sure why
        //return (int (*)( result_of<w<T,N>>... ));
        //return (int (*)( result_of<w<T,N>()>::type... ));

        // Idea #7
        //  Use std::function
        // Result
        //  Can't get function pointer from std::function

        // Idea #2 implementation
        //using F<T,I> = T;
        //return (int (*)( F<T,I>... ))nullptr;

        // So far, only this works:
        return (int (*)( Temp<T,I>... ))nullptr;
    }
    (std::make_index_sequence<N>{});

int main () {
    auto b = a<int, 4>;
    std::cout << typeid(b).name() << std::endl;
}
2 Answers

You can replace Temp<T,I> with std::enable_if_t<(void(I), true), T>.


Function type declarations won't parse extra parentheses

that actually works! Why?

Types can't be enclosed in parentheses. But the first argument of enable_if_t is an expression rather than a type, so ( ) is allowed there.

This is pretty easy with Boost.Mp11:

template <typename... Args>
using into_func = int(Args...);

template <typename T, size_t N>
using result = mp_apply<func_type, mp_repeat_c<mp_list<T>, N>>;

mp_repeat_c<mp_list<T>, N> gives us mp_list<T, T, T, ..., T> with N parameters, and then we mp_apply into_func on that, which turns it into the function type we want.


As to why a few of your other versions didn't work:

This one:

decltype(w<T,N>())...

where:

auto w = []<typename T1, int N1>() -> T1 {};

That's not how you call that lambda. The call operator is a template, but you can't provide the template parameters like that. You have to write w.operator()<T, N>(). If you did that, that probably works.

Or you could do:

auto w = [](size_t) -> T { };

And then use decltype(w(N))....

Although it would be better to write something closer to a valid lambda, like:

auto w = [](size_t) { return std::type_identity<T>{}; };

And then use typename decltype(w(N))::type...


This approach:

result_of<w<T,N>()>::type...

result_of is used like result_of<F(Args...)> where F is a function or function object type. For instance:

struct F { double operator()(int); };
result_of_t<F(int)>; // this is double

This just isn't anything like that here at all - it's just a misuse of result_of. result_of is also deprecated in favor of invoke_result, which wouldn't work with this lambda as specified anyway since again it takes template parameters rather than function parameters.

With my last rewrite tho:

typename std::invoke_result_t<decltype(w), decltype(N)>::type...

Note both the decltype(w) (since you need a type, not an object) and the decltype(N) (since again, type not value).

Related