Любые переносные трюки для получения имени пространства имен в 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.