为什么我们在使用宏时找不到合适的运算符重载?
Why do we not find the right operator overload when using the macro?
我正在编写一个 class 模板,它采用任意函数指针作为非类型模板参数。我想用
template <auto F> struct Foo;
但我的编译器 (MSVC 2017.5) 不支持模板参数列表中的 auto
(即使它支持许多 C++17 功能)。所以我以某种方式围绕这个写了一个 hack:
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>
我实现了一个流式运算符(用于 QDebug
或任何其他文本流),如下所示:
template <typename T, Func<T> F>
QDebug &operator <<(QDebug &out, const FOO(F) &foo)
{
...
return out;
}
但是我的主代码找不到正确的operator<<
重载:
void func(int) { ... }
...
FOO(&func) foo;
qDebug() << foo; // error
令人惊讶的是,定义运算符时一切正常
QDebug &operator <<(QDebug &out, const Foo<Func<T>, F> &foo)
// ^^^^^^^^^^^^^^^
最后一行中的 Func<T>
和宏中的 decltype<F>
似乎没有给出相同的类型。但是我检查了这个并且 std::is_same_v<Func<int>, decltype(&func)>
给出了 true。我想不出为什么使用宏 FOO
会给我任何其他编译时行为,就像不使用它时一样。
一个最小的工作示例:
#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
}
当没有使用宏时,第二个模板参数 F
可以从第二个函数参数 foo
推导出来。当使用宏时,第二个模板参数 F
不能从第二个函数参数中推导出来,因为它会出现在 decltype
内部:Foo<decltype(F), F> & foo
。您的代码可以简化为
template<typename T>
void f(decltype(T) v){}
int v{};
f(v);
编译器知道参数的类型 (int
) 但是模板参数 T
不能从已知的参数类型推导出来,因为在 decltype
T
内部使用时必须提前知道。
解决方法:
template <typename T, Func<T> F>
struct Foo<Func<T>, F> {
friend QDebug &operator <<(QDebug &out, const Foo &foo){
...
return out;
}
};
作为 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 of P
are deduced from the type of the value.
此规则允许以下示例在 C++17 中工作,因为我们可以从 V
的类型推导出 T
:
template <typename T, T V>
struct constant { };
template <typename T, T V>
void foo(constant<decltype(V), V> ) { }
int main() {
foo(constant<int, 4>{});
}
在 C++14 及更早版本中,此示例格式错误,因为未推导 T
。当您使用该宏时,您正试图(隐含地)使用这种行为,它扩展为:
template <typename T, Func<T> F>
std::ostream &operator <<(std::ostream &out, const Foo<decltype(F), F> &foo);
您正试图从 F
中推导出 T
。由于 MSVC 尚不支持 template auto
,因此它也不支持使 template auto
工作所需的机器的其他部分,这也许不足为奇。这就是宏和非宏替代方案之间的区别,可以简单地推断出 T
:
template <typename T, Func<T> F>
std::ostream &operator <<(std::ostream &out, const Foo<Func<T>,F> &foo)
所以简单的解决方案就是...不要使用宏,因为您有一个有效的表单,即使它更冗长。一个更长的解决方案是使用 完全回避整个推导问题。
我正在编写一个 class 模板,它采用任意函数指针作为非类型模板参数。我想用
template <auto F> struct Foo;
但我的编译器 (MSVC 2017.5) 不支持模板参数列表中的 auto
(即使它支持许多 C++17 功能)。所以我以某种方式围绕这个写了一个 hack:
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>
我实现了一个流式运算符(用于 QDebug
或任何其他文本流),如下所示:
template <typename T, Func<T> F>
QDebug &operator <<(QDebug &out, const FOO(F) &foo)
{
...
return out;
}
但是我的主代码找不到正确的operator<<
重载:
void func(int) { ... }
...
FOO(&func) foo;
qDebug() << foo; // error
令人惊讶的是,定义运算符时一切正常
QDebug &operator <<(QDebug &out, const Foo<Func<T>, F> &foo)
// ^^^^^^^^^^^^^^^
最后一行中的 Func<T>
和宏中的 decltype<F>
似乎没有给出相同的类型。但是我检查了这个并且 std::is_same_v<Func<int>, decltype(&func)>
给出了 true。我想不出为什么使用宏 FOO
会给我任何其他编译时行为,就像不使用它时一样。
一个最小的工作示例:
#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
}
当没有使用宏时,第二个模板参数 F
可以从第二个函数参数 foo
推导出来。当使用宏时,第二个模板参数 F
不能从第二个函数参数中推导出来,因为它会出现在 decltype
内部:Foo<decltype(F), F> & foo
。您的代码可以简化为
template<typename T>
void f(decltype(T) v){}
int v{};
f(v);
编译器知道参数的类型 (int
) 但是模板参数 T
不能从已知的参数类型推导出来,因为在 decltype
T
内部使用时必须提前知道。
解决方法:
template <typename T, Func<T> F>
struct Foo<Func<T>, F> {
friend QDebug &operator <<(QDebug &out, const Foo &foo){
...
return out;
}
};
作为 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.
此规则允许以下示例在 C++17 中工作,因为我们可以从 V
的类型推导出 T
:
template <typename T, T V>
struct constant { };
template <typename T, T V>
void foo(constant<decltype(V), V> ) { }
int main() {
foo(constant<int, 4>{});
}
在 C++14 及更早版本中,此示例格式错误,因为未推导 T
。当您使用该宏时,您正试图(隐含地)使用这种行为,它扩展为:
template <typename T, Func<T> F>
std::ostream &operator <<(std::ostream &out, const Foo<decltype(F), F> &foo);
您正试图从 F
中推导出 T
。由于 MSVC 尚不支持 template auto
,因此它也不支持使 template auto
工作所需的机器的其他部分,这也许不足为奇。这就是宏和非宏替代方案之间的区别,可以简单地推断出 T
:
template <typename T, Func<T> F>
std::ostream &operator <<(std::ostream &out, const Foo<Func<T>,F> &foo)
所以简单的解决方案就是...不要使用宏,因为您有一个有效的表单,即使它更冗长。一个更长的解决方案是使用