在另一个命名空间中定义符号

3
这是我的问题:在头文件中,我定义了一个结构体模板type_to_string,其目的是为给定的类型参数定义相应的字符串:
namespace foo {

    template <typename T>
    struct type_to_string
    {
        static const char * value;
    };
}

template <typename T>
const char * foo::type_to_string<T>::value = "???";

我还为字符串定义了一个默认值。

现在,我想使用宏来定义新类型:

#define CREATE_ID(name)                               \
struct name;                                          \
                                                      \
template<>                                            \
const char * foo::type_to_string<name>::value = #name;

问题在于我希望宏在命名空间中可用,例如:
namespace bar
{
    CREATE_ID(baz)
}

这是不可能的,因为type_to_string<T>::value必须在包含foo的命名空间中定义。

这是我得到的编译错误:

[COMEAU 4.3.10.1] error: member "foo::type_to_string<T>::value [with T=bar::baz]"
cannot be specialized in the current scope

[VISUAL C++ 2008] error C2888: 'const char *foo::type_to_string<T>::value' :
symbol cannot be defined within namespace 'bar'
     with
     [
         T=bar::baz
     ]

奇怪的是,GCC 4.3.5(MinGW版本)没有产生任何错误。

有人知道解决办法吗?也许可以使用一些我不知道的查找规则来解决(例如在宏中声明type_to_string,以便每个命名空间都有自己的版本,或者类似的方法)?

2个回答

9
根据C++标准14.7.3/2规定:
显式特化应在模板所属的命名空间中声明,或者对于成员模板,在包含类或包含类模板所属的命名空间中声明。类模板的成员函数、成员类或静态数据成员的显式特化应在类模板所属的命名空间中声明。这样的声明也可以是定义。如果声明不是定义,则特化可以稍后在声明显式特化的命名空间中或在包围显式特化声明的命名空间中定义。
#define DECL_ID(name) \
struct name;                                          

#define CREATE_ID(name) \
template<>              \
const char * foo::type_to_string<name>::value = #name;

namespace bar { namespace bar2 {
    DECL_ID(baz)
} }
CREATE_ID(bar::bar2::baz)

或者

#define CREATE_ID(ns, name)     \
namespace ns { struct name; }   \
                                \
template<>                      \
const char * foo::type_to_string<ns::name>::value = #name;

CREATE_ID(bar, baz)

第三个选项是前两个选项的叠加。它允许在value中使用未经过资格认证的名称(如果需要):
#define DECL_ID(name) \
struct name;                                          

#define CREATE_ID(ns, name) \
template<>              \
const char * foo::type_to_string<ns::name>::value = #name;

namespace bar { namespace bar2 {
    DECL_ID(baz)
} }
CREATE_ID(bar::bar2, baz)

感谢您提供标准段落,确切的要求总是很好的。关于您的第二个解决方案,实际上我已经在做了:我有两个参数,一个用于定义命名空间,另一个用于类型。然而,我希望宏可以在另一个命名空间中使用,以便不限制使用的命名空间数量。我可以使用1、2、3等参数来定义CREATE_ID宏以接受多个命名空间名称,但这将相当繁琐。如果这是唯一的解决方案,我将没有太多选择... - Luc Touraille
记录一下,最终我使用了Boost.Preprocessor来将命名空间列表传递给宏。这样可以将名称封装在多个嵌套的命名空间中。 - Luc Touraille

1

这是我采用的解决方案,使用 Boost.Preprocessor:

#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>

#define BEGIN_NS(r, _, elem)  namespace elem {
#define CLOSE_NS(z, n, _)     }
#define APPEND_NS(r, _, elem) elem::

#define CREATE_ID(ns_list, name)                         \
                                                         \
BOOST_PP_SEQ_FOR_EACH(BEGIN_NS, ~, ns_list)              \
    struct name;                                         \
BOOST_PP_REPEAT(BOOST_PP_SEQ_SIZE(ns_list), CLOSE_NS, ~) \
                                                         \
template<>                                               \
const char * devs::type_to_string<                       \
    BOOST_PP_SEQ_FOR_EACH(APPEND_NS, ~, ns_list)name     \
>::value = #name;

必须像这样在任何命名空间之外使用:

CREATE_ID((bar) (bar2), baz)

我觉得很奇怪,我不得不定义一个宏来重复n次字符“}”,如果有更优雅的方法,请随时发表评论!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接