【问题标题】:Resolving circular dependency between concept and constrained template function解决概念和约束模板函数之间的循环依赖
【发布时间】:2022-08-24 01:28:46
【问题描述】:

我正在尝试更多地了解概念。我在概念和受约束的模板函数之间遇到了一些循环依赖问题,我在一个简单的示例中重现了这些错误。


我有一个概念,Printable,当且仅当operator<< 是在一个类型上定义时,我才希望得到满足。我在可打印类型的向量上也有operator<< 的重载。

令我惊讶的是,std::vector<int> 不被视为 Printable,即使 operator<< 可以在上面工作。


#include <iostream>
#include <vector>

template <class T>
concept Printable = requires(std::ostream& out, T a) {
    out << a;
};

template <Printable T>
std::ostream& operator<<(std::ostream& out, const std::vector<T>& vec) {
    out << \'[\';

    for (std::size_t i {}; i < vec.size(); i++) {
        out << vec[i];
        if (i < vec.size() - 1) {
            out << \", \";
        }
    }

    return out << \']\';
}


static_assert(Printable<int>); // This works as expected.
static_assert(Printable<std::vector<int>>); // This fails.

int main() { 
    std::vector<int> vec {1, 2, 3, 4};
    std::cout << vec << \'\\n\'; // This works as expected.
}

这在 Clang++ 14.0.6_1 上失败,并显示以下消息:

stack_overflow/problem.cpp:26:1: error: static_assert failed
static_assert(Printable<std::vector<int>>); // This fails.
^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~
stack_overflow/problem.cpp:26:15: note: because \'std::vector<int>\' does not satisfy \'Printable\'
static_assert(Printable<std::vector<int>>); // This fails.
              ^
stack_overflow/problem.cpp:7:9: note: because \'out << a\' would be invalid: call to function \'operator<<\' that is neither visible in the template definition nor found by argument-dependent lookup
    out << a;
        ^
1 error generated.

所以我的问题是:如果TPrintable,我该怎么做才能使std::vector&lt;T&gt; 被视为Printable


笔记:

  • 我相信这与 g++ 一样编译得很好,但我最近搞砸了我的 GCC 设置,所以我目前无法确认这一点。如果这是真的,我很想知道为什么它适用于 g++ 而不是 clang++。

    • 更新:Barry 的评论提醒我存在编译器资源管理器。我现在可以确认上面的代码可以在 g++ 上编译,但不能在 clang++ 上编译。我仍然很好奇为什么存在这种差异。
  • 我相信我需要将运算符重载放在Printable 的声明之上。如果我这样做并删除约束,代码编译得很好。但是,如果可能,我想保留 Printable 约束,因为我相信保留这样的约束将在将来简化错误消息。

  • 我怀疑你正在与 ADL 发生冲突。
  • @wojand:是的,在注释掉 static_asserts 的情况下,std::cout &lt;&lt; vec &lt;&lt; \'\\n\' 确实找到并使用了我的实现。它打印[1, 2, 3, 4]。 (等等,他们去哪儿了?我发誓我看到他们问这个...)

标签: c++ c++20 c++-concepts


【解决方案1】:

令我惊讶的是,std::vector&lt;int&gt; 不被视为 Printable,尽管 operator&lt;&lt; 可以在上面工作。

它没有。反正也不是。当您说std::cout &lt;&lt; x; 有效时,您真正的意思是您可以从任何地方编写该表达式并且该表达式有效——“从任何地方”包括Printable 的定义。而在Printable 的定义中,它......不起作用。不合格的查找找不到它,而依赖于参数的查找也找不到它。在大多数其他情况下可能也是如此,除非您在适当的位置小心地添加using operator&lt;&lt;;

您可以尝试将operator&lt;&lt; 的声明向前移动,以便概念定义可以看到它——但这仍然不能最终解决任何其他代码实际上能够调用该运算符的问题。它不能真的除非在namespace std 中声明了此运算符,否则可以正常工作。而且您不允许在其中添加它。

但是如果你可以,那么这将正常工作:

namespace std {
    template <class T>
    concept Printable = requires (ostream os, T const var) { os << var; }

    template <Printable T>
    ostream& operator<<(ostream&, vector<T> const&) { ... }
}

或者只使用{fmt}

【讨论】:

  • 当您说没有其他代码实际上可以调用我的操作员时,我有点困惑。如果我将我拥有的内容放入头文件vector_print.h,那么在包含该文件的任何地方我都可以使用运算符,对吗?
  • @JackCasey 你能够使用它,但前提是您在需要的地方小心地添加using ::operator&lt;&lt;;godbolt.org/z/sjeGGnoPY
  • 啊,这个例子很有帮助,谢谢。此外,您的回答帮助我找到了让static_assert(Printable&lt;std::vector&lt;int&gt;&gt;) 快乐的核心问题的不同解决方案,稍后我将在下面详细说明。
【解决方案2】:

在考虑了巴里的回答后,我继续试验并发现了一些可行的方法:



#include <iostream>
#include <vector>

template <class T>
void print(std::ostream& out, T a) {
    out << a;
}

template <class T>
concept Printable = requires(std::ostream& out, T a) {
    print(out, a);
};

template <Printable T>
std::ostream& operator<<(std::ostream& out, const std::vector<T>& vec) {
    out << '[';

    for (std::size_t i {}; i < vec.size(); i++) {
        out << vec[i];
        if (i < vec.size() - 1) {
            out << ", ";
        }
    }

    return out << ']';
}


static_assert(Printable<int>); // This works as expected.
static_assert(Printable<std::vector<int>>); // This works now.

int main() { 
    std::vector<int> vec {1, 2, 3, 4};
    std::cout << vec << '\n'; // This works as expected.

    // And to really prove it, this works too!
    std::vector<std::vector<int>> vec_of_vecs {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    std::cout << vec_of_vecs << '\n';
}

Clang 编译它,程序输出:

[1, 2, 3, 4]
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

如您所见,诀窍是让Printable 依赖另一个函数print()

老实说,我仍然没有很好的方式来解释为什么operator&lt;&lt; 的查找委托给另一个函数在这里会有所帮助,但确实如此。如果有人可以帮助解释这一点,我会很高兴。

更新: 这行不通。它编译并运行 find,但 Printable 现在没用了,因为它总是通过。

【讨论】:

    猜你喜欢
    • 2012-03-09
    • 2012-11-16
    • 2022-09-29
    • 1970-01-01
    • 2016-10-05
    • 2013-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多