Pass a template template argument without specifying the concrete type
Asked Answered
S

5

10

I would like to implement a wrapper around a function call that would do something like this:

template <template<class> class F>
void Wrapper(int n, F&& f)
{
    switch (n)
    {
        case 1:
            f<std::int8_t>();
            break;
        case 2:
            f<std::int16_t>();
            break;
        default:
            break;
    }
}

template <class T>
void func()
{
    // ... body of func()
}

So that I would be able to make the following call in the code:

Wrapper(1, func);

But the abovementioned code doesn't compile because F&& f construct is invalid - I need to specify the concrete type of the argument. But if I make the function signature the following:

template <template<class> class F, class T>
void Wrapper(int n, F<T>&& f)

then I must make the call with the concrete type of f:

Wrapper(1, func<std::int8_t>);

and I won't be able to make a switch in Wrapper.

How can I implement the behaviour I need?

Saccharometer answered 18/10, 2018 at 14:13 Comment(1)
There is no way to do this unless n is known at compile time. Then, you could just make it a parameter, and call like Wrapper<1>(f). Not sure what you are trying to do here, but the example could also be achieved in better ways.Limacine
H
7

If you know func at compile time (that is, if it is not some function pointer), you can use the following solution:

template <template <class> typename F>
void Wrapper(int n) {
  switch (n) {
    case 1: F<std::int8_t>{}(); break;
    case 2: F<std::int16_t>{}(); break;
    default: break;
  }
}

template <typename T>
void func() { std::cout << sizeof(T) << std::endl; }

template <typename T>
struct Func { void operator()() { func<T>(); } };

int main() {
  Wrapper<Func>(1);
  Wrapper<Func>(2);
}
How answered 18/10, 2018 at 14:32 Comment(0)
L
2

You might rewrite your code to something like

template <typename T> struct tag { using type = T;};

template <class F>
void Wrapper(int n, F&& f)
{
    switch (n)
    {
        case 1:
            f(tag<std::int8_t>());
            break;
        case 2:
            f(tag<std::int16_t>());
            break;
        default:
            break;
    }
}

template <class T>
void func()
{
    // ... body of func()
}

With usage:

Wrapper(1, [](auto t){ return func<typename decltype(t)::type>()});
Lyndes answered 18/10, 2018 at 14:30 Comment(0)
T
2

There are a few kinds of names in C++.

There are values. There are types. There are functions (and methods). There are members. There are type templates. There are function templates. There are variable templates.

These are not the official names in the standard.

In template cases, when the template is instantiated you get the thing the template produces. So a type template when instantiated gives a type, etc.

Members (functions and variables) can become values by becoming a pointer to a member function/value. Member variable names can become a value by pairing with an object, like foo.x.

A function name can become a value by undergoing overload resolution. In some cases the function name is not overloaded, so the overload resolution is trivial.

Etc.

Function arguments are always values. They are never templates.

Template arguments can be types, type templates or values.

Your code is trying to pass a template as a value. You cannot do this without cheating. In your specific case, you want to pass a template function.

Function templates are very much second class citizens in C++. You cannot pass a function template to any other template, nor can you pass them as a value. What you can do with them is highly limited.

So we need to wrap up the template in something that can be passed around.

Here is one approach:

template<class T>struct tag_t{using type=T;};
template<class T>constexpr tag_t<T> tag{};
template<class Tag>using type_t=typename Tag::type;

auto func_template = [](auto...tags) {
  return [](auto&&...args)->decltype(auto) {
    return func< type_t<decltype(tags)>... >( decltype(args)(args)... );
  };
};

now func_template wraps a template function of kind template<class...Ts> R f(Args...) for nearly arbitrary types R, Args... and Ts...

If we assume our argument is wrapped this way, we do this:

template <class F>
void Wrapper(int n, F&& f)
{
    switch (n)
    {
        case 1:
            f(tag<std::int8_t>)();
            break;
        case 2:
            f(tag<std::int16_t>)();
            break;
        default:
            break;
    }
}

then at point of call we do:

Wrapper( 2, func_template );

and it works.

We can generate func_template using macros:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  ->decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

#define FUNC_TEMPLATE(...) \
[](auto...tags) { \
  return [](auto&&...args) \
    RETURNS( __VA_ARGS__< type_t<decltype(tags)>... >( decltype(args)(args)... ) ); \
}

and now we can change the call site to:

Wrapper( 2, FUNC_TEMPLATE(func) );
Tooth answered 18/10, 2018 at 15:40 Comment(0)
B
0

Can you move int n to template parameter? Then you can use Int2Type idiom:

template<int n> struct ParamType {
    using value = ... // here will be std::int8_t or std::int16_t depending on n
}

template <template<class> class F, int n>
void Wrapper(F f) {
    f<ParamType<n>::value>();
}
Billboard answered 18/10, 2018 at 14:23 Comment(1)
F&& f doesn't compile anywaySaccharometer
S
0

and I won't be able to make a switch in Wrapper.

How can I implement the behaviour I need?

Unfortunately you can't pass a template function, to another function, without fix the template types.

But there are many ways to do what you want using template classes (or structs).

By example, following your request based on a template-template argument, you can put a static function inside a template struct (I suggest static, so you don't need to object of that type)

template <typename>
struct foo
 {
   static void func ()
    {
      // ... body of func()
    }
 };

and you have to pass foo explicating it as template parameter

Wrapper<foo>(1);
Wrapper<foo>(2);

Wrapper() become

template <template <typename> class F>
void Wrapper (int n)
{
    switch (n)
    {
        case 1:
            F<std::int8_t>::func();
            break;
        case 2:
            F<std::int16_t>::func();
            break;
        default:
            break;
    }
}

Another way is make a not-template class/struct with a template method

struct bar
 {
   template <typename>
   static void func ()
    {
      // ... body of func()
    }
 };

that you can use in the same way

Wrapper<bar>(1);
Wrapper<bar>(2);

or, if you prefer, in deducing the type using a bar object

bar  b0;

Wrapper(bar{}, 1);
Wrapper(b0,    2);

In first case the signature for Wrapper is simply

template <typename F>
void Wrapper (int n)

where in second case become

template <typename F>
void Wrapper (F const &, int n)

In both case you have to explicate the template calling func()

        F::template func<std::int8_t>();

        // ...

        F::template func<std::int16_t>();
Stimulate answered 18/10, 2018 at 16:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.