is_error_code_enum<> 枚举只能在全局命名空间中定义?

is_error_code_enum<> enumeration must be defined in global namespace only?

我正在尝试创建我自己的基于枚举的错误类别,并希望我的带有错误代码的枚举在某个命名空间内定义。令我惊讶的是,这阻止了我的枚举值自动转换为 std::error_code(如果枚举是在全局命名空间中定义的,则这种转换有效)。

#include <system_error>

namespace NS { enum class X {a,b,c}; }
using NS::X;

class X_category : public std::error_category
{
public:
    virtual const char *name() const noexcept override { return "X"; }
    virtual std::string message(int ev) const override { return ""; }
};

inline const std::error_category& X_category()
{
    static class X_category c; return c;
}

template<> struct std::is_error_code_enum<X> : public std::true_type{};

inline std::error_code make_error_code(X result)
{
    return std::error_code(static_cast<int>(result), X_category());
}

int main()
{
    std::error_code e = X::a; // does not work !!
}

我是否在上面的代码中遗漏了一些东西(可能与重载解析规则有关)以使其工作?或者 std::is_error_code_enum<> 的枚举只能在全局命名空间内定义??

编辑。我的编译器(MSVC2013)没有抱怨它,但似乎 std::is_error_code_enum<> 的专业化必须在 std 命名空间内完成。此外,我在 name() 方法上添加了 noexcept 关键字,使代码更加符合 C++11(MSVC2013 不理解 noexcept,但 MSVC2015 会)。

编辑2。根据 C++11 14.7.3.2 [temp.expl.spec]:

An explicit specialization shall be declared in a namespace enclosing the specialized template.

因此没有必要将 std::is_error_code_enum<> 的特化放在 std 命名空间中。 MSVC 正确编译它,但 GCC 抱怨这实际上是 GCC 中的一个错误,因为 GCC 遵循更严格的旧 C++03 规则。

我认为问题在于大部分设置代码需要位于名称空间内。此代码在 ideone 中为我编译和运行:

#include <system_error>
#include <iostream>

namespace NS {
    enum X {a,b,c};

    class X_category : public std::error_category
    {
    public:
        virtual const char *name() const noexcept override { return "X"; }
        virtual std::string message(int ev) const override { return "M"; }
    };

    inline std::error_code make_error_code(X result)
    {
        return std::error_code(static_cast<int>(result), X_category());
    }
}

namespace std {
    template<> struct is_error_code_enum<NS::X> : public true_type{};
}

int main()
{
    std::cout << NS::X::a;
    std::error_code e = NS::X::a;
    std::cout << e.value();
}

不幸的是,我仍然无法理解 system_error,所以我无法解释为什么,例如,使用枚举 class 而不是简单的枚举会给我一个未指定的运行时错误。

来自 error_code 的构造函数模板确实被考虑在重载决策中 - 您正确地特化了 is_error_code。问题是在 error_codes 构造函数模板定义的这一行中的 ADL:

*this = make_error_code(__e);

ADL 不考虑全局命名空间,因为X 仅在NS 中定义,而不是全局命名空间。 [basic.lookup.argdep]/(2.3):

If T is an enumeration type, its associated namespace is the innermost enclosing namespace of its declaration. [..]

using 声明不会改变这一点。 [basic.lookup.argdep]/2:

The sets of namespaces and classes is determined entirely by the types of the function arguments (and the namespace of any template template argument). Typedef names and using-declarations used to specify the types do not contribute to this set.

要解决此问题,请将您的 make_error_code 添加到 NS:

namespace NS {
    inline std::error_code make_error_code(X result)
    {
        return std::error_code(static_cast<int>(result), X_category());
    }
}

Demo.