我是否可以仅针对尚未指定该行为的类型限制模板运算符?

Can I restrict a template operator only for types that do not have that behaviour already specified?

假设我想为所有类型实现 operator<<。我会这样做:

template <typename T>
std::ostream& operator<<(std::ostream& out, T&& t) {
    return out << "DEFAULT";
}

但是由于歧义(在已经指定的 operator<< 是自由函数的情况下),这将不起作用。所以我试图用 concept:

来限制它
template <typename T>
concept printable = requires(const T& t, std::ostream& out) {
    out << t;
};

这正确地报告了 ints、std::strings 和诸如此类的东西是可打印的,但是 std::vectors 或 some_user_defined_structs(没有超载 <<)不是。

我想要的是将此 concept 与我的(过于通用的)operator<<:

一起使用
#include <iostream>
#include <vector>

template <typename T>
concept printable = requires(const T& t, std::ostream& out) {
    out << t;
};

template <typename T>
requires (!printable<T>)
std::ostream& operator<<(std::ostream& out, T&& t) {
    return out << "DEFAULT";
}

int main() {
    std::cout << std::vector<int>();
}

但这会导致:

In instantiation of 'std::ostream& operator<<(std::ostream&, T&&) [with T = const char (&)[8]]':
recursively required from 'std::ostream& operator<<(std::ostream&, T&&) [with T = const char (&)[8]]'
required from 'std::ostream& operator<<(std::ostream&, T&&) [with T = const char (&)[8]]'
required from here
fatal error: template instantiation depth exceeds maximum of 900 (use '-ftemplate-depth=' to increase the maximum)
    6 |     out << t;
      |     ~~~~^~~~
compilation terminated.

好像有一个实例化循环。为了检查我们是否应该使用我的<<,正在检查printable,这样做会尝试生成<<,这会导致循环。

是否有任何机制可以防止这种循环?我们能否以仅在需要时生成 template 的方式来约束类型?至于用例,假设出于某种原因,当有人试图 << 某些东西到 std::cout.

时,我绝不希望编译失败

当且仅当未提供操作时,您才能提供操作。这本质上是自递归的。

您可以做的是添加另一层间接。像这样:

template <typename T>
void print(std::ostream& os, T&& t) {
    if constexpr (printable<T>) {
        os << t;
    } else {
        os << "DEFAULT";
    }
}

我正在解决这个问题,并且能够想出类似于问题描述的内容,唯一的区别是您必须通过 using namespace 指令选择使用它。 (godbolt demo)

#include <iostream>
#include <vector>
#include <utility>

template <typename T>
// T can be a reference type
concept printable = requires(std::ostream& out, T t) {
    out << std::forward<T>(t);
};

template <typename T>
requires (!printable<T>)
std::ostream& default_print(std::ostream& out, T&& t) {
    return out << "DEFAULT";
}

namespace default_ostream
{

template<typename T>
std::ostream& operator<<(std::ostream& out, T&& t)
requires requires { default_print(out, std::forward<T>(t)); }
{
    return default_print(out, std::forward<T>(t));
}

} // namespace default_ostream

int main()
{
    using namespace default_ostream;
    std::cout << std::vector{ 0, 1, 2 } << '\n';
    std::cout << "Hello!\n";
    std::cout << 2.234 << '\n';
}

此程序将使用所有 GCC、Clang 和 MSVC 输出以下内容:

DEFAULT
Hello!
2.234

将默认 operator<< 放在单独的命名空间中并将 !printable<T> 要求推迟到 print_generic 似乎可行。有了这个,如果你想要这种行为,你将不得不做 using namespace default_ostream;,它不能出现在全局范围内,但在功能(或某些命名空间)范围内很好。

之所以可行,是因为 printable 在用作 default_print 的要求时看不到通用 operator<<,这样就无法为 [=20= 选择] 并避免递归实例化。
当你想使用泛型 operator<< 时,你必须使用 using namespace default_ostream; 将它带入本地范围,因此它参与了重载决议,并且由于它的要求,只有在没有其他 operator<<可用。