MSVC 中的 Constexpr 友元函数

Constexpr friend functions in MSVC

我正在尝试在模板中定义 constexpr 友元运算符。尝试在非 constexpr 上下文中实例化此运算符时出现编译器错误。如果我将完全相同的运算符定义为模板 class 成员作为免费模板函数,它工作正常。

template <typename T>
struct A
{
    T Value;

    // error
    friend constexpr bool operator== (const A& l, const A& r)
    {
        return l.Value == r.Value;
    }

    // ok
    constexpr bool operator!= (const A& r) const
    {
        return Value != r.Value;
    }
};

// ok
template <typename T> constexpr bool
operator< (const A<T>& l, const A<T>& r)
{
    return l.Value < r.Value;
}

#include <string>
int main ()
{
    A<std::string> s;
    bool ret = (s < s, s == s, s != s);
}

我得到的错误是

<source>(7): error C3615: constexpr function 'operator ==' cannot result in a constant expression

<source>(9): note: failure was caused by call of undefined function or one not declared 'constexpr'

<source>(9): note: see usage of 'std::operator =='

<source>(8): note: while compiling class template member function 'bool operator ==(const A<std::string> &,const A<std::string> &)'

<source>(29): note: see reference to class template instantiation 'A<std::string>' being compiled

Godbolt link

这种'friend'歧视是标准的要求还是编译器错误?

我认为,您收到的错误消息可能具有误导性或至少令人困惑。 您的代码的问题是不正确的 friend-声明。

将模板化结构中的运算符声明为 friend 使其成为一个自由函数,就像您示例中的 operator< 一样,因此有两个参数而不是只有一个参数,就像它是operator!= 如您在示例中声明的那样。如果您将 operator< 声明为 struct A 的朋友,正确的做法是:

template <typename  X>
friend constexpr bool operator< (const A<X>& l, const A<X>& r);

operator==也是如此。正确的声明是:

template <typename X>
friend constexpr bool operator== (const A<X>& l, const A<X>& r)
{
    return l.Value == r.Value;
}

即WITH template <typename X> 你在有问题的例子中遗漏了它,因此没有编译。您最初的 operator== 声明不会为 struct A.

生成合适的无友元函数运算符

包含修复的完整代码清单如下:

template <typename T>
struct A
{
    T Value;

    // no error anymore
    template <typename X>
    friend constexpr bool operator== (const A<X>& l, const A<X>& r)
    {
        return l.Value == r.Value;
    }

    // ok
    constexpr bool operator!= (const A& r) const
    {
        return Value != r.Value;
    }
};

你也可以像下面这样声明它

template <typename T>
friend constexpr bool operator== (const A<T>& l, const A<T>& r)

with T 而不是 X 但实际上是一样的,因为内部 T 覆盖外部 T.

[dcl.constexpr] 是这样说的:

If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is still a constexpr function or constexpr constructor, even though a call to such a function cannot appear in a constant expression.

所以用 std::string 实例化你的 class 模板是完全可以的,你的比较函数仍然是 constexpr,尽管对它们的调用是 而不是 常量表达式(通过声明 ret constexpr 来检查)。标记这些函数 constexpr 根本不会给你买任何东西(在这个实例中),但它是完全合法的。

标准接着说

If no specialization of the template would satisfy the requirements for a constexpr function or constexpr constructor when considered as a non-template function or constructor, the template is ill-formed, no diagnostic required.

这似乎不适用于您的情况,例如实例化内置类型确实满足 constexpr 要求。

MSVC的编译错误好像不合理。它看起来像是编译器中的错误。