有一个“静态模板”类,用于枚举到字符串的转换。
下面的代码在enum.cpp中生成了一组不可读的模板相关错误:
//helpers.hpp
namespace helpers {
template<typename T>
class Convert {
public:
static std::string toStr(T) { return m_convert.at(T); }
private:
static std::map<T, std::string> m_convert;
};
}
...
//enum.hpp
namespace A::B::C::D {
enum class ImportantEnum {
item1,
item2,
};
}
...
//enum.cpp
#include "helpers.hpp"
#include "enum.hpp"
namespace A::B::C::D {
template<>
std::map<ImportantEnum, std::string> helpers::Convert<ImportantEnum>::m_convert = {
{ImportantEnum::item1, "item1"},
{ImportantEnum::item2, "item2"},
}
}
...
//enum_lover.cpp
#include "helpers.hpp"
#include "enum.hpp"
namespace A::B::C::D {
void do() {
std::string s = helpers::Convert<ImportantEnum>::toStr(ImportantEnum::item1);
}
}
如果我在enum.cpp中使用下面的代码,一切都很好:
//enum.cpp
#include "helpers.hpp"
#include "enum.hpp"
namespace A::B::C::D {
template<>
std::map<A::B::C::D::ImportantEnum, std::string>
helpers::Convert<A::B::C::D::ImportantEnum>::m_convert = {
{A::B::C::D::ImportantEnum::item1, "item1"},
{A::B::C::D::ImportantEnum::item2, "item2"},
}
}
从名称空间的Angular 来看,有没有可能以更方便的方式实现这种帮助器?
为什么在enum.cpp中需要长“A::B::C::D::ImportantEnum”声明的技巧?它已经在A::B::C::D命名空间中.
更新:
下面是类似的讨论:Why aren't template specializations allowed to be in different namespaces?
1条答案
按热度按时间ovfsdjhp1#
自从我发布了我的原始答案,你已经编辑了你原始问题中的代码。这个答案是对Revision 33的回答。
您所做的一个更改是向枚举常量添加作用域。对于 scoped enumeration,常量必须始终在枚举类名称之前。
你的文件名有点混乱。您命名为
enum.cpp
的文件将被许多程序员命名为helpers.cpp
。这样,类模板Convert
的声明将放在helpers.hpp
中,而其成员的定义将放在helpers.cpp
中。但是,在这种情况下,使用enum.cpp
不会阻止代码编译,因此我保持文件名不变。但是请注意,添加了AnotherEnum
后,在文件enum.cpp
中定义Convert<AnotherEnum>::m_convert
会很麻烦。您链接的问题Why aren't template specializations allowed to be in different namespaces?讨论了标准库中模板的专门化,以及它们应该放在命名空间
std
中的事实。它并不真正适用于你正在努力做的事情。以下是我注意到的其他一些事情。您遇到的许多编译器错误都与这些问题有关。这些笔记的顺序与OP中相应文件的顺序相同。
helpers.hpp
#ifndef HELPERS_HPP
等等。std::map
,但无法包含头<map>
std::string
,但无法包含头<string>
toStr
的定义不提供参数名。我添加了e
,并将其用作函数at
调用中的参数。enum.hpp
#ifndef ENUM_HPP
等等。enum.cpp(第一个版本和第二个版本有相同的问题)
std::map
,但无法包含标头<map>
std::string
,但无法包含头<string>
m_convert
的定义放在命名空间A::B::C::D
中,而不是声明它的命名空间helpers
中。m_convert
的初始化器的右括号之后。enum_lover.cpp
std::string
,但无法包含标头<string>
do
作为函数名。我把它改成了do_it
。你问:
从名称空间的Angular 来看,有没有可能以更方便的方式实现这种帮助器?
一种方法是在源代码中战略性地放置 * 使用声明 *。这就是我在下面的程序中所做的。在文件
helpers.hpp
中,就在函数模板Convert
的声明之前,我为程序使用的两个enum
添加了using声明。使用类模板专门化的解决方案
下面是一个基于OP中代码的运行程序。为了使事情更有趣,我添加了第二个枚举,并将其放在不同的名称空间中。
对头文件
helpers.hpp
的唯一修改(除了上述修复之外)是包含头文件AnotherEnum.hpp
,并添加ImportantEnum
和AnotherEnum
的使用声明。在文件
enum.cpp
中,我将m_convert
的定义移到命名空间helpers
中。现在,在文件enum.cpp
中没有作用域操作。我为文件
enum_lover.cpp
创建了以下头文件。有趣的是,我没有把它包括在内。它包含在main.cpp
中。在文件
enum_lover.cpp
中,我添加了一个输出语句,以便我们可以看到函数toStr
的结果。我认为我们从ADL(参数依赖查找)中得到了一些帮助,因为否则,A::AnotherEnum
应该需要作用域。最后,我们有函数
main
,它调用函数do_it
来运行测试。下面是输出:
只要给予我syntantic糖!
一种更简短、更友好的方法是完全摆脱类模板。使用函数重载即可。您可以在定义每个枚举时定义它们(简单),或者将它们全部收集在一个单独的文件中(更麻烦)。
如果将每个函数定义与它使用的枚举保留在同一个命名空间中,则将最小化命名空间作用域。在最坏的情况下,你将不得不要么作用于函数名,要么作用于它的参数,但不能同时作用于两者。
这些函数都是内联声明的,这样它们就可以在头文件中定义,而不会产生重复的定义错误。
由于枚举常量的数量较少,简单的
switch
语句可能比使用std::map
更有效。我想你必须测试,然而,以确保。