提问人:Anders Brodin 提问时间:10/10/2023 最后编辑:Anders Brodin 更新时间:10/13/2023 访问量:166
在另一个命名空间中创建静态模板类的“实例”
Creating an "instance" of a static template class inside an another namespace
问:
有一个“静态模板”类,用于枚举到字符串的转换。
下面的代码在枚举中生成一组不可读的模板相关错误.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"},
}
}
从命名空间的角度来看,是否有可能以更方便的方式实现这种帮助程序?
为什么在 enum.cpp 中需要长“A::B::C::D::ImportantEnum”声明的技巧?它已经在 A::B::C::D namespase 中......
更新:
下面是类似的讨论:为什么不允许模板专用化位于不同的命名空间中?
答:
由于我发布了我的原始答案,因此您已经编辑了原始问题中的代码。此答案是对修订版 33 的答复。
所做的一项更改是向枚举常量添加范围。使用作用域枚举时,常量必须始终以枚举类名开头。
您的文件名有点令人困惑。您命名的文件将由许多程序员命名。这样,类模板的声明将进入 ,而其成员的定义将进入 。但是,在这种情况下,使用 不会阻止代码编译,因此我保留了文件名不变。但是请注意,添加 后,在文件中定义 是很尴尬的。enum.cpp
helpers.cpp
Convert
helpers.hpp
helpers.cpp
enum.cpp
AnotherEnum
Convert<AnotherEnum>::m_convert
enum.cpp
您链接的问题“为什么不允许模板专用化位于不同的命名空间中?”讨论了标准库中模板的专用化,以及它们应放置在命名空间中的事实。它并不真正适用于您正在尝试做的事情。std
以下是我注意到的其他一些事情。您遇到的许多编译器错误都与这些问题有关。这些笔记的顺序与OP中相应文件的顺序相同。
助手.hpp
- 需求包括 -guard,例如 ,等等。
#ifndef HELPERS_HPP
- 使用 ,但无法包含标头
std::map
<map>
- 使用 ,但无法包含标头
std::string
<string>
- 函数的定义不提供参数名称。我添加了 ,并将其用作函数调用中的参数。
toStr
e
at
枚举.hpp
- 需求包括 -guard,例如 ,等等。
#ifndef ENUM_HPP
枚举 .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
你问:
从命名空间的角度来看,是否有可能以更方便的方式实现这种帮助程序?
一种方法是战略性地将 using 声明放在源代码中。这就是我在下面的程序中所做的。在文件中,就在函数模板声明之前,我为程序使用的两个 s 添加了 using 声明。helpers.hpp
Convert
enum
namespace helpers {
using A::B::C::D::ImportantEnum;
using A::AnotherEnum;
template<typename T>
class Convert { ... };
}
使用类模板专用化的解决方案
这是一个基于 OP 中代码的正在运行的程序。为了让事情更有趣一些,我添加了第二个枚举,并将其放在不同的命名空间中。
// enum.hpp
#ifndef ENUM_HPP
#define ENUM_HPP
namespace A::B::C::D {
enum class ImportantEnum {
item1,
item2,
};
}
#endif
// end file: enum.hpp
// AnotherEnum.hpp
#ifndef ANOTHER_ENUM_HPP
#define ANOTHER_ENUM_HPP
namespace A {
// Note that this enum is not in the same namespace
// as ImportantEnum. That should make things more interesting.
enum class AnotherEnum {
another_item1,
another_item2,
};
}
#endif
// end file: AnotherEnum.hpp
对标头的唯一修改(除了上面描述的修复)是包含标头 ,并添加 和 的 using 声明。helpers.hpp
AnotherEnum.hpp
ImportantEnum
AnotherEnum
// helpers.hpp
#ifndef HELPERS_HPP
#define HELPERS_HPP
#include <string>
#include <map>
#include "enum.hpp"
#include "AnotherEnum.hpp"
namespace helpers {
// Add using declarations for the enumerations you need
// to convert into strings. This simplifies the definitions
// of `m_convert` given in file `enum.cpp`, and may help
// with certain ADL operations as well (see `enum_lover.cpp`).
using A::B::C::D::ImportantEnum;
using A::AnotherEnum;
template<typename T>
class Convert {
public:
static std::string toStr(T e) { return m_convert.at(e); }
private:
static std::map<T, std::string> m_convert;
};
}
#endif
// end file: helpers.hpp
在文件中,我将 的定义移动到命名空间中。现在,文件中没有作用域操作。enum.cpp
m_convert
helpers
enum.cpp
// enum.cpp
#include <map>
#include <string>
#include "helpers.hpp"
#include "enum.hpp"
#include "AnotherEnum.hpp"
namespace helpers {
template<>
std::map<ImportantEnum, std::string> Convert<ImportantEnum>::m_convert = {
{ ImportantEnum::item1, "item1" },
{ ImportantEnum::item2, "item2" },
};
template<>
std::map<AnotherEnum, std::string> Convert<AnotherEnum>::m_convert = {
{ AnotherEnum::another_item1, "another_item1" },
{ AnotherEnum::another_item2, "another_item2" },
};
}
// end file: enum.cpp
我为文件创建了以下标头。有趣的是,我没有把它包括在那里。它包含在 中。enum_lover.cpp
main.cpp
// enum_lover.hpp
#ifndef ENUM_LOVER_HPP
#define ENUM_LOVER_HPP
namespace A::B::C::D {
void do_it();
}
#endif
// end file: enum_lover.hpp
在文件中,我添加了一个输出语句,这样我们就可以看到函数的结果了。我认为我们从 ADL(参数相关查找)中获得了一些帮助,因为否则,应该需要范围界定。enum_lover.cpp
toStr
A::AnotherEnum
// enum_lover.cpp
#include <iostream>
#include <string>
#include "enum.hpp"
#include "AnotherEnum.hpp"
#include "helpers.hpp"
namespace A::B::C::D {
void do_it() {
std::string s = helpers::Convert<ImportantEnum>::toStr(ImportantEnum::item1);
std::cout
<< helpers::Convert<ImportantEnum>::toStr(ImportantEnum::item1) << '\n'
<< helpers::Convert<ImportantEnum>::toStr(ImportantEnum::item2) << '\n'
<< helpers::Convert<AnotherEnum>::toStr(AnotherEnum::another_item1) << '\n'
<< helpers::Convert<AnotherEnum>::toStr(AnotherEnum::another_item2) << '\n';
}
}
// end file: enum_lover.cpp
最后,我们有函数 ,它调用函数来运行测试。main
do_it
// main.cpp
#include "enum_lover.hpp"
int main()
{
A::B::C::D::do_it();
return 0;
}
// end file: main.cpp
输出如下:
item1
item2
another_item1
another_item2
只要给我合成糖!
一种更短、更友好的方法是完全摆脱类模板。只需使用函数重载即可。您可以在定义点为每个枚举定义它们(简单),也可以将它们全部收集到一个单独的文件中(更麻烦)。
如果将每个函数定义保留在与其使用的枚举相同的命名空间中,则可以最大程度地减少命名空间范围。在最坏的情况下,您必须确定函数名称或其参数的作用域,但不能同时限定两者。
这些函数都是内联声明的,因此可以在标头中定义它们,而不会产生重复的定义错误。
// enum.hpp
#ifndef ENUM_HPP
#define ENUM_HPP
#include <map>
#include <string>
namespace A::B::C::D {
enum class ImportantEnum {
item1 = 42,
item2,
};
inline std::string toStr(ImportantEnum const e) {
static std::map<ImportantEnum, std::string> lookup{
{ ImportantEnum::item1, "item1" },
{ ImportantEnum::item2, "item2" },
};
return lookup.at(e);
}
}
#endif
// end file: enum.hpp
// AnotherEnum.hpp
#ifndef ANOTHER_ENUM_HPP
#define ANOTHER_ENUM_HPP
#include <map>
#include <string>
namespace A {
enum class AnotherEnum {
another_item1,
another_item2,
};
inline std::string toStr(AnotherEnum const e) {
static std::map<AnotherEnum, std::string> lookup{
{ AnotherEnum::another_item1, "another_item1" },
{ AnotherEnum::another_item2, "another_item2" },
};
return lookup.at(e);
}
}
#endif
// end file: AnotherEnum.hpp
给定枚举常量的数量较少,简单的语句可能比使用 更有效。但是,我想您必须进行测试才能确定。switch
std::map
评论
helpers::m_convert
helpers::Convert<T>::m_convert
do
是一个关键字。 没有意义。 触发语法错误,缺少 ,缺少包含,并且在此代码中触发编译器错误的内容太多,以至于完全不清楚这个问题是关于什么错误。请提供一个最小的可重现示例和编译器错误消息return m_convert[T];
...do conversion...
;