6

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.)

Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120

6 Answers6

11

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.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
7

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;
};
Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120
3

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

MadScientist
  • 3,390
  • 15
  • 19
  • +1 This is a nice variation of the same idea that I used in my answer. However, it might be useful (it depends on the OP's requirements) to make `call`, instead of receiving a `F`, create a local default-constructed instantiation of it. Then, in `foo` we could use `call(t);`. – Cassio Neri Jun 18 '13 at 13:52
1

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.

urzeit
  • 2,863
  • 20
  • 36
  • 1
    Sure `f1(t)` is a valid expression inside `foo()`. It compiles fine. – Ralph Tandetzky Jun 18 '13 at 13:12
  • Template parameter deduction does not work in specific patterns, perhaps this answers your question: http://stackoverflow.com/questions/1268504/why-is-the-template-argument-deduction-not-working-here – urzeit Jun 18 '13 at 13:27
  • @RalphTandetzky: It doesn't matter what *could* be a valid expression as it is used inside of `foo`. Templates are *not macros*; they don't just take anything and paste tokens together afterwards. Your template function takes an `F &&f`. That is a function parameter; it must be a *value*, which has a *type*. Templates neither are values nor have types. Therefore they can't be passed as function parameters. – Nicol Bolas Jun 18 '13 at 14:39
  • @RalphTandetzky: Yes, `f1(t)` is a valid expression due to function template argument deduction. The compiler can infer, based on the type of the parameter `t`, which instantiation of the function template `f1` to call - it replaces the `f1(t)` with `f1(t)`. This is basically just some syntactic sugar. When you want to pass an instantiation of the `f1` template around, the compiler cannot help you, and you have to specify which one you want. – JohannesD Jun 18 '13 at 15:37
1

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" );
}
JohannesD
  • 13,802
  • 1
  • 38
  • 30
0

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.

Community
  • 1
  • 1
Cassio Neri
  • 19,583
  • 7
  • 46
  • 68