编译器错误模板参数列表用于<比较

Compiler mistakes template argument list for < comparison

本文关键字:lt 比较 用于 列表 错误 参数 编译器      更新时间:2023-10-16

我有一个样式为的模板函数

template <int Exponent> DERIVED_TYPE pow(TYPE const x);

此函数在模板结构中内联定义为友元函数:

template <ARGUMENTS>
struct unit {
    typedef unit<ARGUMENTS> type;
    ....
    template <int Exponent>
    friend constexpr unit_pow_t<type, Exponent> pow(type const x) { ... }
};

这是因为取一个单位的值为幂,必须随值一起改变单位。

当我尝试使用它省略Exponent时,我可以看到编译器考虑匹配的候选者:

src/model/Tool.cpp:113:3: error: no matching function for call to 'pow'
                pow(1._m);
                ^~~
src/model/../units/Units.hpp:2266:46: note: candidate template ignored: couldn't infer template argument 'Exponent'
        friend constexpr unit_pow_t<type, Exponent> pow(type const x) {
                                                    ^
/usr/include/math.h:255:8: note: candidate function not viable: requires 2 arguments, but 1 was provided
double  pow(double, double);
        ^

到目前为止,事情正如预期的那样,可以看到模板,但当然需要指定Exponent。然而,当我这样做的时候,意想不到的事情发生了:

src/model/Tool.cpp:113:6: error: comparison between pointer and integer ('double (*)(double, double)' and 'int')
                pow<3>(1._m);
                ~~~^~

编译器将pow视为"double pow(double,double)"的地址,并解释<3作为将函数指针与整数进行比较的意图。问题出现在clang 3.4、3.6和GCC 5.2中。

我的问题是,我如何说服编译器<3> 是模板参数列表吗?

更新

我终于创建了一个最小的例子,很抱歉出现了不完整的问题:

template <int Exp>
struct metre {
    double value;
    template <int Exponent>
    friend constexpr metre<Exp * Exponent> pow(metre<Exp> const x) {
        return {0};
    }
};
int main() {
    pow<2>(metre<1>{1});
    return 0;
};

它似乎没有看到pow:

targs.cpp:11:2: error: use of undeclared identifier 'pow'
        pow<2>(metre<1>{1});
        ^

如果我包括cmath,我有与以前相同的诊断:

targs.cpp:13:5: error: comparison between pointer and integer ('double (*)(double, double)' and 'int')
        pow<2>(metre<1>{1});
        ~~~^~
1 error generated.

因此,"double pow(double,double)"的存在只是掩盖了看不到模板的问题。那么问题来了,为什么pow<>()编译器没有看到?

不需要(也没有逻辑)friend template。使用免费功能:

template <int Exp>
struct metre {
    double value;
};
template<int B, int A>
constexpr auto pow(metre<A> const x) -> metre<A*B>
{
        return metre<A*B>{x.value};
}
int main() {
    metre<2> result = pow<2>(metre<1>{1});
    return 0;
};

coliru上的现场演示。

这是模板朋友的问题,有两个怪癖:函数的名称是pow;模板好友有自己的模板参数!

根据经验,无论何时在类模板中使用friend,都要保持警惕。

为了先解决更容易的问题:如前所述,在MCVE中,metre的类定义不会导致声明名称pow(稍后将对此进行解释)。出现错误消息是因为实际上有一个名称pow的可见声明:它在C标准库头math.h中。错误消息中的"指针"是指向此函数的函数指针。

最好不要将函数的名称与C标准库中的函数相同。无论如何,这些都可能被定义为预处理器宏,从而造成进一步的麻烦。

在这篇文章的其余部分,我将假设MCVE将pow替换为pok,以避免出现这种问题。然后生成一条正常的错误消息:

po.cc:13:5: error: 'pok' was not declared in this scope
    pok<2>(metre<1>{1});
    ^

现在转到主要问题。

这里讨论这个问题的基本版本。问题是,在类模板内声明友元函数不会使友元函数也成为与类模板具有相同参数的模板函数。

事实上,对于类模板的每个实例化,friend声明都为该实例化声明一个非模板friend。

要查看此操作,请在MCVE中添加行metre<1> m;作为main的第一行。这实例化了metre<1>,从而导致template<int Exponent> pok(metre<1>)的存在,因此编译器在下一行识别pok

事实上,任何特定的实例化都是有效的,而不仅仅是metre<1>,因为这至少允许pok的名称查找成功,然后在过载解决(两阶段查找!)发生时,参数metre<1>已经导致metre<1>被实例化。


结束语:我不完全确定上面的解释是正确的——模板朋友很复杂。也许事实证明pok应该被声明,而编译器被窃听了。但我同意rubenvb的建议,即最好不要使用friend来完全避免这种情况。

在前面答案的帮助下,我找到了一个解决方法。

问题是友元已定义(或多或少如预期),但在类/结构范围之外未知。所以编译器找不到它们。

解决方案是在结构外部放置一个声明:

template <int Exp>
struct metre {
    double value;
    template <int Exponent>
    friend constexpr metre<Exp * Exponent> pow(metre<Exp> const x) {
        return {23};
    }
};
template <class> void pow(); // <== HERE BE DRAGONS
int main() {
    static_assert(pow<2>(metre<1>{1}).value == 23, "foobar");
    return 0;
};

请注意,没有必要提供匹配的声明。具有正确名称的函数的任何模板声明似乎都允许编译器发现友元模板函数。