I am writing a class template which takes an arbitrary function pointer as non-type template argument. I would like to use
template <auto F> struct Foo;
but my compiler (MSVC 2017.5) does not support auto
in the template parameter list (even though it supports many C++17 features). So I wrote a hack around this somehow like this:
template <typename T>
using Func = void (*)(T);
template <typename TF, TF F> struct Foo;
template <typename T, Func<T> F>
struct Foo<Func<T>, F> { ... };
#define FOO(func) Foo<decltype(func), func>
I implemented a streaming operator (for QDebug
or any other text stream) like this:
template <typename T, Func<T> F>
QDebug &operator <<(QDebug &out, const FOO(F) &foo)
{
...
return out;
}
But my main code cannot find the right operator<<
overload:
void func(int) { ... }
...
FOO(&func) foo;
qDebug() << foo; // error
Surprisingly, everything works when defining the operator like
QDebug &operator <<(QDebug &out, const Foo<Func<T>, F> &foo)
// ^^^^^^^^^^^^^^^
It seems that Func<T>
from this last line and decltype<F>
from the macro do not give the same type. But I checked this and std::is_same_v<Func<int>, decltype(&func)>
gives true. I cannot think why using the macro FOO
gives me any other compile time behavior as when not using it.
A minimal working example:
#include <iostream>
template <typename T>
using Func = void (*)(T);
template <typename TF, TF F> struct Foo;
template <typename T, Func<T> F>
struct Foo<Func<T>, F> { };
#define FOO(func) Foo<decltype(func), func>
template <typename T, Func<T> F>
std::ostream &operator <<(std::ostream &out, const FOO(F) &foo)
// std::ostream &operator <<(std::ostream &out, const Foo<Func<T>,F> &foo)
{
return out;
}
void func(int);
int main(int argc, char **argv)
{
FOO(&func) foo;
std::cout << foo << std::endl; // error
}
As part of the template auto
paper, we also got a new deduction rule in [temp.deduct.type]:
When the value of the argument corresponding to a non-type template parameter
P
that is declared with a dependent type is deduced from an expression, the template parameters in the type ofP
are deduced from the type of the value.
This rule allows the following example to work in C++17, because we can deduce T
from the type of V
:
template <typename T, T V>
struct constant { };
template <typename T, T V>
void foo(constant<decltype(V), V> ) { }
int main() {
foo(constant<int, 4>{});
}
In C++14 and earlier, this example is ill-formed because T
isn't deduced. It is precisely this behavior that you are trying to use (implicitly) when you use that macro, which expands into:
template <typename T, Func<T> F>
std::ostream &operator <<(std::ostream &out, const Foo<decltype(F), F> &foo);
You are trying to deduce T
from F
. Since MSVC doesn't support template auto
yet, it's perhaps unsurprising that it also doesn't support other parts of the machinery requires to make template auto
work. And that's the difference between the macro and the non-macro alternative, which can simply deduce T
:
template <typename T, Func<T> F>
std::ostream &operator <<(std::ostream &out, const Foo<Func<T>,F> &foo)
So the simple solution is to just... don't use a macro, since you have a working form, even it's more verbose. A longer solution is to use Yakk's answer which side-steps the whole deduction question entirely.