在C++中获取命名空间名称的任何可移植技巧

Any portable tricks to obtain namespace name in C++?

本文关键字:任何 可移植 C++ 获取 命名空间      更新时间:2023-10-16

我有一些格式良好的代码,如下所示:

NAMESPACE_BEGIN(Foo)
inline void test() {
string s = xxx;
}
NAMESPACE_END(Foo)

那么,使用NAMESPACE_BEGIN()宏获取命名空间名称"Foo"是否有任何可移植技巧test()

我正在考虑这样的事情,但它肯定会导致符号重新定义:

#define NAMESPACE_BEGIN(x) 
namespace x { 
inline const char *_curNamespace() { 
return #x; 
}
#define NAMESPACE_END(x) 
}

还有一个解决方法看起来像这样,但这不是很方便

#define NAMESPACE_NAME Foo
// using a header file so that we can use #ifdef guard
#include "MyNamespaceBegin.h"
...
#include "MyNamespaceEnd.h"

编辑

  • 为什么我需要这个:

    我正在使用大部分宏来生成代码以实现一些 动态反射逻辑(是的,不是静态模板反射), 通过使用静态成员函数在类范围内没问题, 但不适用于命名空间

  • 为什么不手动声明一次名称 getter:

    我想要的是这样的东西:

    // the global default version
    const char *_curNamespace() {return "";}
    namespace X {
    // the local namespace version
    const char *_curNamespace() {return "X";}
    // some verbose reflection register code
    ...
    registerSomething(_curNamespace());
    ...
    }
    

    当然,所有详细的寄存器代码都应该由宏生成

    而且,应用级用户不应该关心_curNamespace(), 所以,我想简化用户的使用在任何情况下都使用自定义NAMESPACE_BEGIN(xxx)

  • 如果你仍然对我在做什么感到好奇, 检查这个: https://github.com/ZFFramework/ZFFramework

    我使用了很多技巧来实现纯C++的完全动态反射, 为了实现我的一些奇思妙想, 目前,这个项目只是为了好玩, 我不知道它是否具有实用性

编辑2

目前,我认为最好的解决方法应该是这样的:

#define NAMESPACE_BEGIN(ns) 
namespace ns { 
extern const char *__curNS();
#define NAMESPACE_END(ns) 
}
#define NAMESPACE_REG(ns) 
const char *__curNS() {return #ns;}
  • 应用级用户仍然只需要关心NAMESPACE_BEGIN
  • NAMESPACE_REG必须在源文件中只声明一次
    • 否则,将发生未定义的符号
    • 如果多次,将发生重复的符号
    • 虽然这很烦人,有时你需要额外的源文件 要握住NAMESPACE_REG, 严格的规则应该防止用户忘记丑陋的解决方法

你在实现起来微不足道的事情上大惊小怪。

首先,使用NAMESPACE_BEGINNAMESPACE_END对我来说似乎是不必要的。我不明白这比

namespace Foo
{
}

如果获取namespace的名称很重要/有用,请添加一个简单的函数。

namespace Foo
{
inline std::string get_name() { return "Foo"; }
}

小型实际应用程序需要数千行代码。大型实际应用程序需要数百万行代码。从这个角度来看,实现单行inline功能是一项非常小的任务。

此解决方案采用了一些预处理器魔法,并具有以下功能:

  • 命名空间只被提及一次
  • 访问包含不带引号的名称的宏
  • 访问包含带引号的名称的宏
  • 支持重复相同的命名空间
  • 支持不同的命名空间
  • 检测到开始/结束宏的误用
  • 清理,即在开始/结束块之外没有定义额外的宏

它不支持嵌套命名空间。

用法示例:

#include "framework.hpp"
#define NAMESPACE_NAME Foo
#include NAMESPACE_BEGIN
// Here you have access to NAMESPACE_NAME (unquoted, i.e. Foo)
// and also to NAMESPACE_NAME_STRING (quoted, i.e. "Foo")
#include NAMESPACE_END

// NAMESPACE_NAME and NAMESPACE_NAME_STRING do not exist
// outside the block, so they cannot be misused

// Different namespaces in the same TU are supported
#define NAMESPACE_NAME Bar
#include NAMESPACE_BEGIN
inline std::string f()
{
return NAMESPACE_NAME_STRING;
}
#include NAMESPACE_END

// Repeating the same namespace is also supported
#define NAMESPACE_NAME Foo
#include NAMESPACE_BEGIN
inline std::string f()
{
return NAMESPACE_NAME_STRING;
}
#include NAMESPACE_END

实现如下:

framework.hpp

#pragma once
#define NAMESPACE_BEGIN "framework_namespace_begin.hpp"
#define NAMESPACE_END "framework_namespace_end.hpp"

framework_namespace_begin.hpp

#ifndef NAMESPACE_NAME
#error "NAMESPACE_NAME not defined"
#endif
#define NAMESPACE_IN_NAMESPACE 1
#define NAMESPACE_NAME_DO_STR(X) #X
#define NAMESPACE_NAME_STR(X) NAMESPACE_NAME_DO_STR(X)
#define NAMESPACE_NAME_STRING NAMESPACE_NAME_STR(NAMESPACE_NAME)
namespace NAMESPACE_NAME {

framework_namespace_end.hpp

#ifndef NAMESPACE_IN_NAMESPACE
#error "NAMESPACE_IN_NAMESPACE not defined"
#endif
}
#undef NAMESPACE_NAME
#undef NAMESPACE_NAME_STRING
#undef NAMESPACE_IN_NAMESPACE

你知道吗? 我想我可能有一个可行的解决方案。 它实际上非常简单,并且非常接近OP的原始建议(如果您想在同一翻译单元中打开命名空间两次,实际上只有潜在的重复定义问题)。 您只需要横向思考一下,不要太珍惜看到您的命名空间被宏而不是大括号括起来。

所以让我把它放在这里,因为它真的没有什么,然后我会解释为什么我个人碰巧喜欢它。

法典:

宏:

#define DECLARE_NAMESPACE(ns) 
namespace ns {
static constexpr const char *_curNamespace = #ns; 
}
#define BEGIN_NAMESPACE(ns) 
namespace ns { 
static_assert (ns::_curNamespace, "BEGIN_NAMESPACE: namespace has not been declared");
#define END_NAMESPACE }

示例代码:

#include <iostream>
DECLARE_NAMESPACE (Foo)
BEGIN_NAMESPACE (Foo)
void print_namespace_name () { std::cout << _curNamespace << "n"; }
END_NAMESPACE
BEGIN_NAMESPACE (Foo)
void another_print_namespace_name () { std::cout << _curNamespace << "n"; }
END_NAMESPACE
DECLARE_NAMESPACE (Bar)
BEGIN_NAMESPACE (Bar)
void print_namespace_name () { std::cout << _curNamespace << "n"; }
DECLARE_NAMESPACE (BarBar)
BEGIN_NAMESPACE (BarBar)
void print_namespace_name () { std::cout << _curNamespace << "n"; }
END_NAMESPACE
END_NAMESPACE
int main ()
{
Foo::print_namespace_name ();
Foo::another_print_namespace_name ();
Bar::print_namespace_name ();
Bar::BarBar::print_namespace_name ();
}

输出:

Foo
Foo
Bar
BarBar

现在,这显然非常容易实现,也易于使用,并且没有明显的限制。 特别是,它可以处理嵌套命名空间(如上面的代码所示),并且在同一编译单元中打开命名空间两次也可以工作(同样,这显示在代码片段中)。

但是,但是,但是,我们是否仍然需要两次输入命名空间的名称,这难道不是我们试图避免消除拼写错误的事情吗?

好吧,当然,我们必须输入两次名称,但那又怎样,忍受它。 关键是,有了这组特定的宏,编译器现在将为我们捕获任何拼写错误。 让我们通过故意放一个来证明这一点。 所以这个:

DECLARE_NAMESPACE Whoops
BEGIN_NAMESPACE whoops
END_NAMESPACE

生成这个(我找不到更好的方法来制定static_assert,抱歉):

prog.cc:12:24: error: '_curNamespace' is not a member of 'whoops'
static_assert (ns::_curNamespace, "BEGIN_NAMESPACE: namespace has not been declared");
^~~~~~~~~~~~~
prog.cc:27:5: note: in expansion of macro 'BEGIN_NAMESPACE'
BEGIN_NAMESPACE (whoops)
^~~~~~~~~~~~~~~

更重要的是(这就是为什么我们需要BEGIN_NAMESPACE宏):

DECLARE_NAMESPACE (Bar)
BEGIN_NAMESPACE (Bar)
DECLARE_NAMESPACE (BarWhoops)
BEGIN_NAMESPACE (Barwhoops)
END_NAMESPACE
END_NAMESPACE

生成此内容:

prog.cc:12:24: error: '_curNamespace' is not a member of 'Bar::Barwhoops'
static_assert (ns::_curNamespace, "BEGIN_NAMESPACE: namespace has not been declared");
^~~~~~~~~~~~~
prog.cc:42:5: note: in expansion of macro 'BEGIN_NAMESPACE'
BEGIN_NAMESPACE (Barwhoops)
^~~~~~~~~~~~~~~

这只是花花公子。

所以,你知道,有什么不喜欢的?

现场演示 - 取消注释第 3 行以查看这些编译器错误。

您可以使用变量并使用"NAMESPACE_BEGIN"和"NAMESPACE_END"更改其值

变量__name表示当前的完整命名空间位置

比如"abc::d ef::d etail">

std::string __name = "";
std::string & __append(std::string & str, const char * ptr) {
if (!str.empty()) {
str.append("::");
}
str.append(ptr);
return str;
}
std::string & __substr(std::string & str, const char * ptr) {
if (str.length() == strlen(ptr)) {
str.clear();
} else {
str = str.substr(0, str.length() - strlen(ptr) - 2);
}
return str;
}
#define NAMESPACE_NAME __name
#define CONCATENATE_DIRECT(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_DIRECT(s1, s2)
#ifdef _MSC_VER
# define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __COUNTER__)
#else
# define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__)
#endif 
#define APPEND_NAME(x) std::string ANONYMOUS_VARIABLE(__start_name) = __append(__name, #x)
#define SUBSTR_NAME(x) std::string ANONYMOUS_VARIABLE(__end_name) = __substr(__name, #x)
#define NAMESPACE_BEGIN(x) 
namespace x { 
APPEND_NAME(x); 
#define NAMESPACE_END(x) 
SUBSTR_NAME(x); 
}

然后,您可以使用NAMESPACE_NAME宏作为全名,也可以从中提取姓氏

这里有一种方法。 核心思想来自思路:

问:如何定义可从同一范围访问的具有相同名称的多个事物?

答:使它们都具有不同的参数类型。如果它们都有相同的身体,那么被称为哪一个并不重要。

问:如何生成一组无限的不同参数类型?

答:类模板。

问:如何确保对该重载函数集的调用永远不会模棱两可?

答:确保二元关系"隐式可转换自"是参数类型的完整排序,并为参数类型使用唯一的最小元素。

#include <type_traits>
#include <functional>
struct NamespaceHandleObj {
template <const NamespaceHandleObj* Handle1, const NamespaceHandleObj* Handle2>
struct less : public std::bool_constant<std::less<>{}(Handle1, Handle2)> {};
};
template <>
struct NamespaceHandleObj::less<nullptr, nullptr> : public std::false_type {};
template <const NamespaceHandleObj* Handle1>
struct NamespaceHandleObj::less<Handle1, nullptr> : public std::false_type {};
template <const NamespaceHandleObj* Handle2>
struct NamespaceHandleObj::less<nullptr, Handle2> : public std::true_type {};
template <const NamespaceHandleObj* Handle>
struct NamespaceParamType
{
constexpr NamespaceParamType() noexcept = default;
template <const NamespaceHandleObj* Other,
typename = std::enable_if_t<NamespaceHandleObj::less<Other, Handle>::value>>
constexpr NamespaceParamType(NamespaceParamType<Other>) noexcept {}
};
#define NAMESPACE_UTILS_TOSTR1(x) #x
#define NAMESPACE_UTILS_TOSTR(x) NAMESPACE_UTILS_TOSTR1(x)
#define BEGIN_NAMESPACE(ns) 
namespace ns { 
namespace { 
constexpr NamespaceHandleObj namespace_handle_{}; 
constexpr const char* current_ns_(
NamespaceParamType<&namespace_handle_>) noexcept 
{ return NAMESPACE_UTILS_TOSTR(ns); } 
}
#define END_NAMESPACE }
#define CURRENT_NAMESPACE (current_ns_(NamespaceParamType<nullptr>{}))

上面的代码是 C++17,但将其移植到以前的版本并不难,甚至一直移植到 C++03。