为什么在使用宏时我们找不到合适的运算符重载?

时间:2017-11-19 14:45:36

标签: c++ templates operator-overloading overloading overload-resolution

我正在编写一个类模板,它将任意函数指针作为非类型模板参数。我想用

template <auto F> struct Foo;

但我的编译器(MSVC 2017.5)在模板参数列表中不支持auto(即使它支持许多C ++ 17功能)。所以我就这样写了一个黑客:

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)>给出了真实。我想不出为什么使用宏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
}

3 个答案:

答案 0 :(得分:2)

解决方法:

template <typename T, Func<T> F> 
struct Foo<Func<T>, F> {
  friend QDebug &operator <<(QDebug &out, const Foo &foo){
    ...
    return out;
  }
};

答案 1 :(得分:1)

作为template auto paper的一部分,我们还在[temp.deduct.type]中获得了新的扣除规则:

  

当从表达式推导出与从属类型声明的非类型模板参数P对应的参数的值时,P类型的模板参数是从价值的类型。

此规则允许以下示例在C ++ 17中工作,因为我们可以从T的类型中推导出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>{});
}

在C ++ 14及更早版本中,此示例格式错误,因为未推导出T。当您使用该宏时,您正试图(隐式地)使用这种行为,该宏扩展为:

template <typename T, Func<T> F>
std::ostream &operator <<(std::ostream &out, const Foo<decltype(F), F> &foo);

您正试图从T中推断F。由于MSVC尚不支持template auto,因此它也不足为奇,它也不支持机器的其他部分使template auto工作。这就是宏观和非宏观替代方案之间的区别,它可以简单地推导出T

template <typename T, Func<T> F>
std::ostream &operator <<(std::ostream &out, const Foo<Func<T>,F> &foo)

所以简单的解决方案就是......不要使用宏,因为你有一个工作形式,即使它更冗长。更长的解决方案是使用Yakk's answer,它完全支持整个演绎问题。

答案 2 :(得分:0)

当没有使用宏时,可以从第二个函数参数F推导出第二个模板参数foo。使用宏时,无法从第二个函数参数推导出第二个模板参数F,因为它将显示在decltypeFoo<decltype(F), F> & foo内。您的代码可以简化为

template<typename T>
void f(decltype(T) v){}

int v{};
f(v);

编译器知道参数的类型(int)但是模板参数T不能从已知的参数类型中推断出来,因为在decltype T内部使用时必须是提前知道。