在另一个命名空间中创建静态模板类的“实例”

Creating an "instance" of a static template class inside an another namespace

提问人:Anders Brodin 提问时间:10/10/2023 最后编辑:Anders Brodin 更新时间:10/13/2023 访问量:166

问:

有一个“静态模板”类,用于枚举到字符串的转换。

下面的代码在枚举中生成一组不可读的模板相关错误.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 中......

更新:

下面是类似的讨论:为什么不允许模板专用化位于不同的命名空间中?

C++ 静态 命名空间 模板专用化

评论

1赞 Alan Birtles 10/10/2023
请显示一个最小的可重现示例和错误消息
1赞 463035818_is_not_an_ai 10/10/2023
“静态模板类”不是一回事。它是一个只有静态成员的类模板。由于它没有非静态成员,因此创建它的实例是没有用的。我必须阅读更多内容,也许它只是措辞不对,你想实例化类模板吗?上课?不获取该类的实例?
0赞 Anders Brodin 10/10/2023
此处的“静态模板”是指仅具有静态方法和静态成员的模板。所有工作无需实例化即可完成
0赞 463035818_is_not_an_ai 10/10/2023
没有.不过,这是。看起来重要部分已从代码中删除helpers::m_converthelpers::Convert<T>::m_convert
1赞 463035818_is_not_an_ai 10/10/2023
do是一个关键字。 没有意义。 触发语法错误,缺少 ,缺少包含,并且在此代码中触发编译器错误的内容太多,以至于完全不清楚这个问题是关于什么错误。请提供一个最小的可重现示例和编译器错误消息return m_convert[T];...do conversion...;

答:

0赞 tbxfreeware 10/10/2023 #1

由于我发布了我的原始答案,因此您已经编辑了原始问题中的代码。此答案是对修订版 33 的答复。

所做的一项更改是向枚举常量添加范围。使用作用域枚举时,常量必须始终以枚举类名开头。

您的文件名有点令人困惑。您命名的文件将由许多程序员命名。这样,类模板的声明将进入 ,而其成员的定义将进入 。但是,在这种情况下,使用 不会阻止代码编译,因此我保留了文件名不变。但是请注意,添加 后,在文件中定义 是很尴尬的。enum.cpphelpers.cppConverthelpers.hpphelpers.cppenum.cppAnotherEnumConvert<AnotherEnum>::m_convertenum.cpp

您链接的问题“为什么不允许模板专用化位于不同的命名空间中?”讨论了标准库中模板的专用化,以及它们应放置在命名空间中的事实。它并不真正适用于您正在尝试做的事情。std

以下是我注意到的其他一些事情。您遇到的许多编译器错误都与这些问题有关。这些笔记的顺序与OP中相应文件的顺序相同。

助手.hpp

  • 需求包括 -guard,例如 ,等等。#ifndef HELPERS_HPP
  • 使用 ,但无法包含标头std::map<map>
  • 使用 ,但无法包含标头std::string<string>
  • 函数的定义不提供参数名称。我添加了 ,并将其用作函数调用中的参数。toStreat

枚举.hpp

  • 需求包括 -guard,例如 ,等等。#ifndef ENUM_HPP

枚举 .cpp (第一版和第二版有相同的问题)

  • 使用 ,但无法包含标头std::map<map>
  • 使用 ,但无法包含标头std::string<string>
  • 有点奇怪的是,它将 的定义放在 命名空间 中 ,而不是声明它的命名空间中。m_convertA::B::C::Dhelpers
  • 缺少分号 (;),在 的初始值设定项的右大括号之后。m_convert

enum_lover.cpp

  • 使用 ,但无法包含标头std::string<string>
  • 使用关键字作为函数名称。我把它改成了.dodo_it

你问:

从命名空间的角度来看,是否有可能以更方便的方式实现这种帮助程序?

一种方法是战略性地将 using 声明放在源代码中。这就是我在下面的程序中所做的。在文件中,就在函数模板声明之前,我为程序使用的两个 s 添加了 using 声明。helpers.hppConvertenum

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.hppAnotherEnum.hppImportantEnumAnotherEnum

// 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.cppm_converthelpersenum.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.cppmain.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.cpptoStrA::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

最后,我们有函数 ,它调用函数来运行测试。maindo_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

给定枚举常量的数量较少,简单的语句可能比使用 更有效。但是,我想您必须进行测试才能确定。switchstd::map

评论

0赞 Anders Brodin 10/11/2023
我有几十个枚举分发了其他代码。想法是 - 创建一个模板,并就地重新定义地图......如果我已经在该命名空间中,为什么我需要执行 A::B::C::D:: 定义的问题。为什么第一个示例中列出的代码不正确...
0赞 tbxfreeware 10/11/2023
@Anders Brodin:我更新了我的答案,使其符合您问题的最新编辑。希望这会有所帮助。