17

The code below is rejected by VC++ 2012 with "error C2207: 'A::bar' : a member of a class template cannot acquire a function type".

int Hello(int n)
{
    return n;
}

template<class FunctionPtr>
struct A
{
    A(FunctionPtr foo)
        : bar(foo)
    {}

    FunctionPtr bar;
};

int main()
{
    A<decltype(Hello)> a(Hello);

    return 0;
}

Why?

manlio
  • 18,345
  • 14
  • 76
  • 126
xmllmx
  • 39,765
  • 26
  • 162
  • 323

4 Answers4

10

gcc is a bit more friendly regarding this error :

error: field 'A<int(int)>::bar' invalidly declared function type

The simplest solution is to declare bar as a function pointer :

FunctionPtr *bar;

In this case, decltype(Hello) evaluates to int(int) not int(*)(int).

BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • 5
    So what is the compatible solution? If instead of passing &Hello, he passed a struct that has overloaded the operator(), this would work fine. How does one write generic code that can accept either a function object or a function pointer? – NHDaly Aug 04 '13 at 17:12
  • 1
    @NHDaly `std::function` should work fine, as it takes template parameters in taht format – OMGtechy May 26 '15 at 19:47
  • 1
    @NHDaly A [`std::conditional`](http://en.cppreference.com/w/cpp/types/conditional)-based approach works (you can take a look at my answer for the details). – manlio Dec 06 '17 at 15:54
6

Variables cannot have function types. You declare bar to be FunctionPtr which is decltype(Hello) which evaluates to int (int), not a function pointer type.

It's confusing because of some inconsistencies inherited from C. When you define the constructor for A as taking a FunctionPtr you might imagine you'd get the same error. However, function parameters declared as having an array or function type automatically (unfortunately, inconveniently) get turned into pointer types. So even though foo is declared to have a function type it actually has function pointer type and works fine.

But this rule applies only to function parameters and not other variables, so bar actually does have a function type, which is not legal.

bames53
  • 86,085
  • 15
  • 179
  • 244
4

Adding on the other answers, you can take advantage of the fact that:

The following code:

#include <type_traits>

template<class F>
struct A
{
    A(F foo) : bar(foo) {}

    typename std::conditional<std::is_function<F>::value,
                              typename std::add_pointer<F>::type,
                              F>::type bar;
};

is a generic solution allowing the same syntax for functions, function pointers, functors and lambdas:

#include <type_traits>
#include <iostream>

void Hello() { std::cout << "Function\n"; }

struct Hello2 { void operator()() { std::cout << "Struct\n"; } };

void Hello3() { std::cout << "Function pointer\n"; }

template<class F>
struct A
{
  A(F foo) : bar(foo) { bar(); }

  std::conditional_t<std::is_function<F>::value, std::add_pointer_t<F>, F> bar;
};

int main()
{
  A<decltype(Hello)> a(Hello);

  Hello2 h2;
  A<decltype(h2)> b(h2);

  A<decltype(&Hello3)> c(&Hello3);

  auto Hello4 = []() { std::cout << "Lambda\n"; };
  A<decltype(Hello4)> d(Hello4);
}

(here I've slightly changed the solution taking advantage of C++14 features).

Indeed std::function is a (not always better) alternative.

manlio
  • 18,345
  • 14
  • 76
  • 126
  • In your solution, you use 'T' which is not a template parameter. I think you meant 'F'. – Nibor May 21 '19 at 11:46
0

Just came across this today - if you run it with decltype((Hello)) should be fine without having to change the code. See Scott Meyers for details

Amb
  • 1