Какие-нибудь портативные приемы для получения имени пространства имен в C ++? - PullRequest
0 голосов
/ 05 июля 2018

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

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

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

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

  • Почему бы не объявить имя получателя вручную один раз:

    Я хочу что-то вроде этого:

    // 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 голосов
/ 08 июля 2018

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

0 голосов
/ 07 июля 2018

Вот способ. Основная идея пришла из мышления:

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

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.

0 голосов
/ 05 июля 2018

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

0 голосов
/ 06 июля 2018

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

  • Пространство имен упоминается только один раз
  • Доступ к макросу, содержащему имя без кавычек
  • Доступ к макросу, содержащему имя в кавычках
  • Поддержка повторения того же пространства имен
  • Поддержка различных пространств имен
  • Обнаружено неправильное использование макросов 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
0 голосов
/ 05 июля 2018

Вы много суетитесь из-за чего-то, что тривиально реализовать.

Прежде всего, использование NAMESPACE_BEGIN и NAMESPACE_END кажется мне ненужным. Я не вижу, как это более читабельно или полезно, чем

namespace Foo
{
}

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...