Ralph Tandetzky
Ralph Tandetzky

Reputation: 23610

Why do I need to specify the template argument type of a templated function here?

I have the following code:

template <typename T>
void f1( T t )
{
    std::cout << "f1( " << t << " ) called." << endl;
}

template <typename T>
void f2( T t )
{
    std::cout << "f2( " << t << " ) called." << endl;
}

template <typename F, typename T>
void call( F && f, T t )
{
    f( t );
}

template <typename T>
void foo( T t )
{
    call( f1<T>, t ); // Why is <T> necessary?
                      // f1(t) is a valid expression!
    call( f2<T>, t );
}

void bar()
{
    foo( 1 );
}

In the function foo() I need to specify the template argument, even though f1(t) is a valid expression. That's kinda destroying some possibilities in my code. My questions:

  1. Why do I need to specify the template argument?
  2. How can I work around that limitation? (C++11 or C++14 allowed).

(BTW: I'm currently using Visual Studio 2010 and I get the error C2896, if I leave the <T> out.)

Upvotes: 6

Views: 971

Answers (6)

Ralph Tandetzky
Ralph Tandetzky

Reputation: 23610

1. Why do I need to specify the template argument?

Well, f1 is not an object, but a function template. You can only pass objects to functions.

2. How can I work around that limitation? (C++11 or C++14 allowed).

Use an object with a templated operator(). Just replace the definition of f1() with

struct { template <typename T> void operator()( T t )
{
    std::cout << "f1( " << t << " ) called." << endl;
} } f1;

and likewise for f2(). In C++14 you can write it even nicer

static const auto f1 = []( auto t )
{
    std::cout << "f1( " << t << " ) called." << endl;
};

Upvotes: 7

JohannesD
JohannesD

Reputation: 14421

There is a way to mimic passing function templates (or overload sets) around as first-class values: "reify" them by turning them into function objects. Your code could be rewritten like this:

struct F1
{
  template <typename T>
  void operator ()( T t )
  {
    std::cout << "f1( " << t << " ) called." << endl;
  }
} f1;

struct F2
{
  template <typename T>
  void operator ()( T t )
  {
    std::cout << "f2( " << t << " ) called." << endl;
  }
} f2;

// Note that this function didn't change at all!
template <typename F, typename T>
void call( F && f, T t )
{
    f( t );
}
// Neither did this, expect that now you don't need the <T>
template <typename T>
void foo( T t )
{
    call( f1, t );
    call( f2, t );
}

void bar()
{
    foo( 1 );
    foo( 3.14 );
    foo( "Hello World" );
}

Upvotes: 1

Cassio Neri
Cassio Neri

Reputation: 20503

The explanation why it doesn't work has been given by Angew and Urzeit.

What I'm trying to offer is a possible workaround for your problem. However, I cannot tell exactly whether this is appropriate or not for your case since the OP offers limited information on your specific designing requirements.

The first step is transforming f1 and f2 into template functor classes:

template <typename T>
class f1 {
public:
  void operator ()( T t ) {
      std::cout << "f1( " << t << " ) called." << std::endl;
  }
};

(Similarly for f2.) In this way, you can pass f1 (and not f1<T>) as a template template parameter to call which is now defined in this way:

template <template <typename> class F, typename T>
void call( T t )
{
    F<T> f;
    f( t );
}

Notice that F is a template template parameter that binds to a template class taking one template type parameter (e.g. f1).

Finally, foo becomes this:

template <typename T>
void foo( T t )
{
    call<f1>( t );
    call<f2>( t );
}

bar is left as before.

Upvotes: 0

MadScientist
MadScientist

Reputation: 3460

You could try to wrap the templated functions f1 and f2 in non-templated classes and pass instances (or even types) around, e.g.

struct F1
{
  template <typename T>
  void operator()(T t) const
  {
    std::cout << "F1::operator()(" << t << ") called" << std::endl;
  }
};

struct F2
{
  template <typename T>
  void operator()(T t) const
  {
    std::cout << "F2::operator()(" << t << ") called" << std::endl;
  }
};

template <typename F, typename T>
void call(F && f, T t)
{
  f(t);
}

template <typename T>
void foo(T t)
{
  static const F1 f1;
  static const F2 f2;
  call(f1, t);
  call(f2, t);
}

void bar()
{
  foo(1);
}

which produces the output:

F1::operator()(1) called

F2::operator()(1) called

Upvotes: 3

urzeit
urzeit

Reputation: 2909

f1(t) is not a valid expression, since there is no function f1. There is only a template named f1 from which a function f1<T> can be generated on compile time.

This limitation you talk about is a direct consequence of the compile-time type-checking. If you know the type of the argument of foo at compile time, there is no limitation, since you cann add it easily. If you don't know the type of the argument, you might have to use a derived class model instead of a template-driven idea.

Upvotes: 1

f1 is not a function, it's a template. You cannot pass a template as a function argument.

f1<T> is a function, so it can be passed.

Upvotes: 11

Related Questions