0

我怀疑这个问题的答案是相当明显的,但由于某种原因它让我无法理解。

我试图让fmt库对枚举类型使用通用流插入运算符。fmt默认情况下将枚举格式化为整数,但如果可用(至少在某些情况下),也会对用户定义的类型使用重载的插入运算符。

为了进行实验,我开始时不使用fmt, :

#include <iostream>
#include <ostream>
#include <type_traits>

enum class Enum0 { a, b, c, };

namespace foo {

enum class Enum1 { a, b, c, };

}

template <typename T>
std::enable_if_t<std::is_enum_v<T>, std::ostream &>
operator<<(std::ostream & stream, T const value) {
    return stream << static_cast<std::underlying_type_t<T>>(value);
}

int main() {
    std::cout << Enum0::b << std::endl;
    std::cout << foo::Enum1::c << std::endl;
}

输出:

1
2

这使用带有标准输出流的自定义操作符(操作符被注释掉,它无法按预期编译)。foo::Enum1在这种情况下,即使未在命名空间中定义运算符,也会正确选择运算符foo

然后我做了同样的事情,但补充说fmt

#include <iostream>
#include <ostream>
#include <type_traits>

#include "fmt/format.h"
#include "fmt/ostream.h"

enum class Enum0 { a, b, c, };

namespace foo {

enum class Enum1 { a, b, c, };

}

template <typename T>
std::enable_if_t<std::is_enum_v<T>, std::ostream &>
operator<<(std::ostream & stream, T const value) {
    return stream << "custom operator: "
        << static_cast<std::underlying_type_t<T>>(value);
}

int main() {
    std::cout << Enum0::b << std::endl;
    std::cout << foo::Enum1::c << std::endl;

    fmt::print("{}\n", Enum0::b);
    fmt::print("{}\n", foo::Enum1::c);
}

输出:

custom operator: 1
custom operator: 2
custom operator: 1
2

fmt为全局命名空间中的枚举选择自定义运算符,但不为命名空间中的枚举选择自定义运算符foo

如果我将运算符放在与枚举相同的命名空间中:

#include <iostream>
#include <ostream>
#include <type_traits>

#include "fmt/format.h"
#include "fmt/ostream.h"

namespace foo {

enum class Enum1 { a, b, c, };

template <typename T>
std::enable_if_t<std::is_enum_v<T>, std::ostream &>
operator<<(std::ostream & stream, T const value) {
    return stream << "custom operator: "
        << static_cast<std::underlying_type_t<T>>(value);
}

}

int main() {
    std::cout << foo::Enum1::c << std::endl;

    fmt::print("{}\n", foo::Enum1::c);
}

我得到:

custom operator: 2
custom operator: 2

显示fmt在这种情况下选择重载。但这并没有多大用处,因为重载旨在成为通用解决方案,并且不同的枚举可能位于不同的命名空间中。

似乎是一个类似的问题在这里讨论。提供了两种解决方案。一种是将重载放在std命名空间中。这行得通,但是围绕它的规则相当严格,而且对我来说,这个特定用例是否有效并不是很明显。线程中的另一个解决方案是将运算符放在fmt包含之前。我也试过了:

#include <iostream>
#include <ostream>
#include <type_traits>

template <typename T>
std::enable_if_t<std::is_enum_v<T>, std::ostream &>
operator<<(std::ostream & stream, T const value) {
    return stream << "custom operator: "
        << static_cast<std::underlying_type_t<T>>(value);
}

#include "fmt/format.h"
#include "fmt/ostream.h"

enum class Enum0 { a, b, c, };

namespace foo {

enum class Enum1 { a, b, c, };

}

int main() {
    std::cout << Enum0::b << std::endl;
    std::cout << foo::Enum1::c << std::endl;

    fmt::print("{}\n", Enum0::b);
    fmt::print("{}\n", foo::Enum1::c);
}

但是,尽管它似乎对线程中的参与者有效,但对我来说,fmt仍然没有选择重载。(我认为该线程来自将枚举的默认格式作为整数添加到库之前,因此从那时起这种行为可能已经改变。)

有没有办法(除了将实现放在命名空间中)为将使用的枚举类型std实现单个通用运算符,即使该运算符位于全局命名空间中并且枚举位于另一个命名空间中?我意识到这里可能有一个与查找规则或类似规则有关的非常明显的答案,但如果有解决方案,我会错过它,所以我想我会在这里问。(我知道专业化是枚举格式化的另一种选择,但我对是否可以使用通用解决方案感兴趣。)<<fmtfmt::formatter<<

4

0 回答 0