在C ++中有任何可移植的技巧来获取名称空间名称吗?

时间:2018-07-05 06:32:43

标签: c++ namespaces

我有一些格式正确的代码如下:

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

那么,通过使用NAMESPACE_BEGIN()宏在test()中获得名称空间名称“ Foo”,是否有任何可移植的技巧?

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

#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 ++中的完全动态反射, 实现我的一些幻想 现在,这个项目只是为了好玩, 我不知道它是否具有实用性

EDIT2

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

#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, 严格的规则应防止用户忘记丑陋的解决方法

5 个答案:

答案 0 :(得分:1)

您正在为一些琐碎的实现大惊小怪。

首先,对我来说,NAMESPACE_BEGINNAMESPACE_END的使用似乎是不必要的。我看不出它比它更具可读性或有用性

namespace Foo
{
}

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

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

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

答案 1 :(得分:1)

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

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

它不支持嵌套名称空间。

用法示例:

<asp:ListView ID="ListItems" class="block" SortExpression="DataType" ItemStyle-Wrap="false"  runat="server">
    <ItemTemplate  >
        <table>
            <tr>
                <asp:TextBox ID="Name" runat="server" class="inline" Text='<%# Eval("text") %>'></asp:TextBox>

            </tr>
        </table>
    </ItemTemplate>
</asp:ListView>

实现如下:

#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

答案 2 :(得分:1)

你知道吗?我想我可能对此有一个可行的解决方案。它实际上非常简单,并且非常接近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)
     ^~~~~~~~~~~~~~~

那只是花花公子。

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

Live demo-取消注释第3行以查看那些编译器错误。

答案 3 :(得分:0)

您可以使用变量,并使用“ NAMESPACE_BEGIN”和“ NAMESPACE_END”更改其值

变量__name代表当前的完整名称空间位置

例如“ abc :: def :: detail”

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宏作为全名,也可以从中提取姓氏

答案 4 :(得分:0)

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

  

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

     

A:使它们的所有功能具有不同的参数类型。而且,如果他们所有的人都具有相同的身体,那叫谁都没关系。

     

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

     

A:类模板。

     

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

     

A:确保二进制关系“可以从其隐式转换为”是对参数类型的完整排序,并为参数类型使用唯一的最小元素。

#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。