为什么编译器会报告运算符+的“不相关”候选模板

Why does compiler report "irrelevant" candidate templates for operator+

提问人:samw 提问时间:9/21/2023 更新时间:9/21/2023 访问量:67

问:

我有一个支持任何其他 .widget<T>operator+widget<T>

#include <string>
#include <vector>

template <class T>
struct widget {
  widget<T> operator+(const widget<T>&) {
    return {};
  }
};

当我添加 和 时,我得到预期的简短而简单的错误:widget<int>widget<double>

<source>:14:16: error: invalid operands to binary expression ('widget<int>' and 'widget<double>')
    auto z = x + y;
             ~ ^ ~
<source>:6:15: note: candidate function not viable: no known conversion from 'widget<double>' to 'const widget<int>' for 1st argument
    widget<T> operator+(const widget<T>&) {
              ^

但是,当我尝试添加 a 和 a 时,我收到大量额外的错误,说“候选模板被忽略...”对于一大堆(在我看来)不应该考虑的类型。widget<int>widget<std::vector<int>>

<source>:14:16: error: invalid operands to binary expression ('widget<int>' and 'widget<std::vector<int>>')
    auto z = x + y;
             ~ ^ ~
/opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/bits/stl_iterator.h:630:5: note: candidate template ignored: could not match 'reverse_iterator' against 'widget'
    operator+(typename reverse_iterator<_Iterator>::difference_type __n,
    ^
/opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/bits/stl_iterator.h:1786:5: note: candidate template ignored: could not match 'move_iterator' against 'widget'
    operator+(typename move_iterator<_Iterator>::difference_type __n,
    ^

[... lots more of this ...]

    ^
/opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/bits/basic_string.tcc:627:5: note: candidate template ignored: could not match 'basic_string' against 'widget'
    operator+(_CharT __lhs, const basic_string<_CharT, _Traits, _Alloc>& __rhs)
    ^
<source>:6:15: note: candidate function not viable: no known conversion from 'widget<std::vector<int>>' to 'const widget<int>' for 1st argument
    widget<T> operator+(const widget<T>&) {
              ^

如果当我尝试添加 and 时没有考虑所有这些候选人,为什么现在突然考虑了它们?我能做些什么来获得“更友好”的错误消息吗?widget<int>widget<double>

我在所有 3 个编译器上都看到了这种行为:GCC、Clang 和 MSVC。

在 Compiler Explorer 上查看

C++ Templates 运算符重载

评论

5赞 songyuanyao 9/21/2023
当您提到 时,ADL 会从命名空间中找到更多候选项。stdstd::vector
1赞 samw 9/21/2023
谢谢,您链接的页面很好地描述了它。有没有办法让我修改代码,这样就不会考虑其他候选者?另外,我添加了一个额外的重载要求并标记了它。这适用于 GCC 和 MSVC,但不适用于 clang。!is_same_v<T,U>= delete
0赞 HolyBlackCat 9/21/2023
目前尚不清楚为什么要禁用它们,在这种情况下,除了错误消息之外,它们不会影响其他任何内容。
0赞 songyuanyao 9/21/2023
@samw我不这么认为,因为这是一个语言功能。
1赞 HolyBlackCat 9/21/2023
解决这个问题的办法是.您还可以添加模板作为速记。如果模板参数来自封闭类,则不会将其视为 ADL。template <class T> struct foo { struct widget {...}; };using

答:

1赞 Jan Schultke 9/21/2023 #1

候选人并非无关紧要。候选项是通过参数相关查找 (ADL) 找到的,因为 的模板参数之一是 。ADL 在与模板参数相同的命名空间中查找函数。在您的例子中,ADL 在 中发现大量重载。widgetstd::vectoroperator+namespace std

它们被找到,但它们不是可行的候选项,因此它们包含在错误消息中。如果不对 your 进行一些修改,则无法更改此行为。widget

解决方案 A - 防止 ADL

您可以将 your 包装在帮助程序类模板中,以便模板参数不再直接属于:widgetwidget

template <class T>
struct outer {
    struct widget {
        // possible, but not advised:
        // widget operator+(const widget&) { return {}; }

        // better: (hidden friend)
        friend widget oprator+(const widget&, const widget&) { return {}; }
    };
};

// helper alias template for convenience
template <class T>
using widget = typename outer<T>::widget; // typename is unnecessary since C++20 here

使用此解决方案时,模板参数将属于 ,而不是 ,这可以防止 ADL 在 中查找所有这些重载。std::vectorouterwidgetnamespace std

解决方案 B -static_assert

另一种选择是创建一个接受任何小部件组合并通过类型组合是否有效的小部件。operator+static_assert

template <class T>
struct widget {
    // TODO: if necessary, declare the function template as a friend here
};

template <class L, class R>
widget<L> operator+(const widget<L>&, const widget<R>&) {
    static_assert(std::is_same_v<L, R>, "Adding two widgets requires them to have the same type");
    return {};
}

这可行,但它对 SFINAE 不友好,即您无法使用表达式或其他方式测试表达式是否对两个小部件有效。它始终有效,但实例化可能会导致编译器错误。a + brequires