Любые переносные трюки для получения имени пространства имен в C++?

У меня есть хорошо сформированный код, который выглядит так:

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"

EDIT:

  • Зачем мне это нужно:

    Я использую большую часть макроса для генерации кодов для достижения некоторой логики динамического отражения (да, а не статического отражения шаблона), все в порядке в пределах класса, используя статическую функцию-член, но не работает для пространств имен

  • Почему бы не вручную объявить имя 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, строгое правило должно помешать пользователю забыть уродливое обходное решение

Ответы

Ответ 1

Вы очень беспокоитесь о чем-то, что тривиально реализовать.

Во-первых, использование NAMESPACE_BEGIN и NAMESPACE_END кажется мне ненужным. Я не понимаю, насколько это более читаемо или полезно, чем

namespace Foo
{
}

Если получение имени namespace важно/полезно, добавьте тривиальную функцию.

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

Маломасштабные приложения реального мира нуждаются в тысячах строк кода. Крупномасштабные приложения реального мира нуждаются в миллионах строк кода. С этой точки зрения реализация одной строки inline функции является очень незначительной задачей.

Ответ 2

Это решение использует немного препроцессорной магии и имеет следующие возможности:

  • Пространство имен упоминается только один раз
  • Доступ к макросу, содержащему неупомянутое имя
  • Доступ к макросу, содержащему указанное имя
  • Поддержка повторения одного и того же пространства имен
  • Поддержка разных пространств имен
  • Обнаружено неправильное использование макросов BEGIN/END
  • Очистка, то есть никаких дополнительных макросов, определенных вне блока BEGIN/END

Он не поддерживает вложенные пространства имен.

Пример использования:

#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

Ответ 3

Знаешь что? Я думаю, что у меня может быть просто жизнеспособное решение для этого. Это на самом деле очень просто, и это очень близко к исходному предложению 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 - uncomment line 3, чтобы увидеть эти ошибки компилятора.

Ответ 4

вы можете использовать переменную и изменить ее значение с помощью "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 для полного имени или вы можете извлечь из него фамилию

Ответ 5

Вот путь. Основная идея исходила из линии мысли:

В: Как я могу определить несколько вещей с одним и тем же именем, доступным из той же области?

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>{}))

Код выше - С++ 17, но было бы непросто перенести его в предыдущие версии, вплоть до С++ 03.