【问题标题】:Why do we not find the right operator overload when using the macro?为什么我们在使用宏时找不到正确的运算符重载?
【发布时间】:2017-11-19 14:45:35
【问题描述】:

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

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&lt;&lt;重载:

void func(int) { ... }

...

FOO(&func) foo;
qDebug() << foo; // error

令人惊讶的是,当定义类似这样的运算符时,一切正常

QDebug &operator <<(QDebug &out, const Foo<Func<T>, F> &foo)
//                                     ^^^^^^^^^^^^^^^

似乎最后一行中的Func&lt;T&gt; 和宏中的decltype&lt;F&gt; 给出的类型不同。但我检查了这一点,std::is_same_v&lt;Func&lt;int&gt;, decltype(&amp;func)&gt; 给出了真实的答案。我想不出为什么使用宏 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
}

【问题讨论】:

  • 打开编译器的预处理器输出,看看FOO 宏产生了什么。这是预先查看正在编译的内容的唯一方法。
  • @PaulMcKenzie 产生Foo&lt;decltype(F), F&gt; 不是很“明显”吗?或者你是什么意思?
  • 不,这不是“显而易见的”。宏是邪恶的野兽,可以做你意想不到的事情。检查预处理器输出——没有其他替代品可以准确地查看编译器实际编译的内容。
  • @M.Winter 这不是最小的或完整的。您应该能够编写一个小程序来重现您的问题,例如,不依赖于 Qt。将代码片段分成多个块也很难回答,因为现在我们必须收集片段来重现。

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


【解决方案1】:

解决方法:

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

【讨论】:

    【解决方案2】:

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

    当从表达式推导非类型模板形参P所声明的依赖类型的实参值时,P类型的模板形参从价值。

    此规则允许以下示例在 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)
    

    所以简单的解决方案是……不要使用宏,因为你有一个工作表单,即使它更冗长。更长的解决方案是使用Yakk's answer,它完全避开了整个演绎问题。

    【讨论】:

      【解决方案3】:

      当没有使用宏时,第二个模板参数F可以从第二个函数参数foo推导出来。当使用宏时,第二个模板参数F 不能从第二个函数参数推导出来,因为它会出现在decltype:Foo&lt;decltype(F), F&gt; &amp; foo 的内部。您的代码可以简化为

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

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

      【讨论】:

      • 嗯。为什么编译器不知道decltype(F) 是模板参数列表中指定的Func&lt;T&gt;?这是类型推导工作方式的一些微妙之处吗?
      • @M.Winter 那时编译器还不知道FT 并试图推断它们。因此,一旦编译器遇到需要编译器试图推断为已知的模板参数类型的东西,推断就会停止。
      • 你简化得太多了。另一个模板参数也可以用来推导,OP的代码更接近this
      • @Barry 其他模板参数不能用于推导,因为此时也没有推导。真正的问题是使用decltype
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-13
      • 1970-01-01
      • 2012-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多