c++ 在另一个名称空间中创建静态模板类的“示例”

ycl3bljg  于 2023-10-21  发布在  其他
关注(0)|答案(1)|浏览(94)

有一个“静态模板”类,用于枚举到字符串的转换。
下面的代码在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?

ovfsdjhp

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

  • 需要include-guard,例如#ifndef HELPERS_HPP等等。
  • 使用std::map,但无法包含头<map>
  • 使用std::string,但无法包含头<string>
  • 函数toStr的定义不提供参数名。我添加了e,并将其用作函数at调用中的参数。
    enum.hpp
  • 需要include-guard,例如#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声明。

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

对头文件helpers.hpp的唯一修改(除了上述修复之外)是包含头文件AnotherEnum.hpp,并添加ImportantEnumAnotherEnum的使用声明。

// 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

在文件enum_lover.cpp中,我添加了一个输出语句,以便我们可以看到函数toStr的结果。我认为我们从ADL(参数依赖查找)中得到了一些帮助,因为否则,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

只要给予我syntantic糖!
一种更简短、更友好的方法是完全摆脱类模板。使用函数重载即可。您可以在定义每个枚举时定义它们(简单),或者将它们全部收集在一个单独的文件中(更麻烦)。
如果将每个函数定义与它使用的枚举保留在同一个命名空间中,则将最小化命名空间作用域。在最坏的情况下,你将不得不要么作用于函数名,要么作用于它的参数,但不能同时作用于两者。
这些函数都是内联声明的,这样它们就可以在头文件中定义,而不会产生重复的定义错误。

// 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更有效。我想你必须测试,然而,以确保。

相关问题