Построить путь для директивы #include с макросом

Я хотел бы включить пути файлов, динамически создаваемые макросом для части, зависящей от целевой конфигурации моей программы.

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

#include TARGET_PATH_OF(header.h)

Что будет расширяться до следующего:

#include "corefoundation/header.h"

когда источник сконфигурирован (в данном случае) для OSX

До сих пор все попытки потерпели неудачу. Я надеюсь, что кто-то там сделал это раньше?

пример того, что не работает:

#include <iostream>
#include <boost/preprocessor.hpp>

#define Dir directory/
#define File filename.h

#define MakePath(f) BOOST_PP_STRINGIZE(BOOST_PP_CAT(Dir,f))
#define MyPath MakePath(File)

using namespace std;

int main() {
    // this is a test - yes I know I could just concatenate strings here
    // but that is not the case for #include
    cout << MyPath << endl;
}

ошибки:

./enableif.cpp:31:13: error: pasting formed '/filename', an invalid preprocessing token
    cout << MyPath << endl;
            ^
./enableif.cpp:26:16: note: expanded from macro 'MyPath'
#define MyPath MakePath(File)
               ^
./enableif.cpp:25:40: note: expanded from macro 'MakePath'
#define MakePath(f) BOOST_PP_STRINGIZE(BOOST_PP_CAT(Dir,f))
                                       ^
/usr/local/include/boost/preprocessor/cat.hpp:22:32: note: expanded from macro 'BOOST_PP_CAT'
#    define BOOST_PP_CAT(a, b) BOOST_PP_CAT_I(a, b)
                               ^
/usr/local/include/boost/preprocessor/cat.hpp:29:36: note: expanded from macro 'BOOST_PP_CAT_I'
#    define BOOST_PP_CAT_I(a, b) a ## b
                                   ^
1 error generated.

Ответ 1

Я склонен согласиться с комментарием в ответе utnapistim, что вы не должны делать это, даже если вы можете. Но, на самом деле, вы можете это сделать со стандартными C-компиляторами. [Примечание 1]

Есть две проблемы, которые нужно преодолеть. Во-первых, вы не можете использовать оператор ## для создания чего-то, что не является допустимым токеном препроцессора, а имена путей не квалифицируются как допустимые токены препроцессора, поскольку они содержат символы / и .. (. будет в порядке, если токен начинается с цифры, но / никогда не будет работать.)

На самом деле вам не нужно объединять токены, чтобы привести их в соответствие с оператором #, так как этот оператор будет структурировать весь аргумент макроса, а аргумент может состоять из нескольких токенов. Однако stringify учитывает пробелы [Примечание 2], поэтому STRINGIFY(Dir File) не будет работать; это приведет к "directory/filename.h" а посторонние пробелы в имени файла приведут к #include. Так что вам нужно объединить Dir и File без пробелов.

Следующее решает вторую проблему, используя функциональный макрос, который просто возвращает свой аргумент:

#define IDENT(x) x
#define XSTR(x) #x
#define STR(x) XSTR(x)
#define PATH(x,y) STR(IDENT(x)IDENT(y))

#define Dir sys/
#define File socket.h

#include PATH(Dir,File)

Предупреждение: (Спасибо @jed за разрешение этой проблемы.) Если объединяемые строки содержат идентификаторы, которые в других местах определены как макросы, то здесь произойдет неожиданная замена макроса. Следует соблюдать осторожность, чтобы избежать этого сценария, особенно если Dir и/или File не контролируются (например, определяясь как параметр командной строки в вызове компилятора).

Вы также должны знать, что некоторые реализации могут определять слова, которые могут отображаться в виде токенов в пути к файлу. Например, GCC может определять макросы с именами, такими как unix и linux если он не вызывается с явным стандартом C (который не используется по умолчанию). Это может быть вызвано путями, такими как platform/linux/my-header.h или даже linux-specific/my-header.h.

Чтобы избежать этих проблем, я бы порекомендовал, если вы используете этот хак:

  • вы используете настройку компилятора, соответствующего стандартам C (или C11), и

  • Вы размещаете последовательность очень рано в исходном файле, в идеале перед тем, как включать любой другой заголовок или, по крайней мере, любой заголовок за пределы стандартной библиотеки.

Кроме того, вам не понадобится усложнение макроса IDENT если вы можете написать объединение без пробелов. Например:

#define XSTR(x) #x
#define STR(x) XSTR(x)

#define Dir sys
#define File socket.h

#include STR(Dir/File)

Заметки

  1. Я попробовал это с помощью clang, gcc и icc, как доступно на godbolt. Я не знаю, работает ли это с Visual Studio.

  2. Точнее говоря, он полу-уважает пробелы: пробелы преобразуются в один символ пробела.

Ответ 2

Я хотел бы включить пути файлов, динамически создаваемые макросом для части, зависящей от целевой конфигурации моей программы.

Вы должны быть неспособны (и если вы в состоянии это сделать, вы, вероятно, не должны этого делать).

Фактически вы пытаетесь выполнить задание компилятора в исходном файле, что не имеет большого смысла. Если вы хотите изменить включить пути на основе машины, с которой вы компилируете, это решаемая проблема (но не решена в файле заголовка).

Каноническое решение:

Используйте IF в своем файле Makefile или CMakeLists.txt, используйте страницы настраиваемых свойств в зависимости от конфигурации сборки в Visual Studio (или просто установите конкретные настройки для вашей сборки в среде ОС для своего пользователя).

Затем напишите директиву include как:

#include <filename.h> // no path here

и полагаться на систему среды/сборки, чтобы сделать путь доступным при вызове компилятора.

Ответ 3

Из вашего описания, похоже, вы обнаружили, что не каждая "" является строкой. В частности, #include "corefoundation/header.h" выглядит как обычная строка, но это не так. Грамматически цитируемый текст вне директив препроцессора предназначен для компилятора и скомпилирован с нулевыми завершенными строковыми литералами. Цитированный текст в директивах препроцессора интерпретируется препроцессором определенным образом.

Тем не менее, ошибка в вашем примере состоит в том, что Boost вставил второй и третий токены: / и filename. Первый, четвертый и пятый токены (directory, . и h) остаются неизменными. Очевидно, это не то, что вы хотели.

Гораздо проще использовать автоматическую конкатенацию строк. "directory/" "filename" - это тот же строковый литерал, что и "directory/filename" Обратите внимание, что между двумя фрагментами нет +.

Ответ 4

Это работает для VS2013. (Это может быть сделано проще, конечно.)

#define myIDENT(x) x
#define myXSTR(x) #x
#define mySTR(x) myXSTR(x)
#define myPATH(x,y) mySTR(myIDENT(x)myIDENT(y))

#define myLIBAEdir D:\\Georgy\\myprojects\\LibraryAE\\build\\native\\include\\ //here whitespace!
#define myFile libae.h

#include myPATH(myLIBAEdir,myFile)