Обходные пути использования __has_include, когда он может быть определен или не определен. Допустимо ли передавать __hasC++

Программы на C++. Форум разработчиков
Ответить
Anonymous
 Обходные пути использования __has_include, когда он может быть определен или не определен. Допустимо ли передавать __has

Сообщение Anonymous »

Часто мне хотелось бы объединить условные выражения препроцессора в цепочку с использованием __has_include (определенного в C++17 и C23 и поддерживаемого в более ранних версиях в качестве расширения многими компиляторами (GCC 4.9.2 и выше, а Clang как минимум 3.0.0)). Простой пример выглядит примерно так:

Код: Выделить всё

#if defined(__has_include) && __has_include("header.h")
#include "header.h"
#else
#include "backup-header.h"
#endif
Проблема в том, что это не компилируется для компиляторов/стандартов, которые не поддерживают __has_include. Проверка define(__has_include) нам не помогает, поскольку фраза __has_include("header.h") изначально не является синтаксически допустимой, если __has_include не определен.
В документации GCC предлагается что-то вроде следующего:

Код: Выделить всё

#if defined __has_include
#  if __has_include ()
#    include 
#  endif
#endif
https://gcc.gnu.org/onlinedocs/cpp/_005 ... clude.html
Проблема в том, что становится сложно связать ветку #else. Если бы мы хотели адаптировать его к нашему примеру, нам нужно было бы определить дополнительный макрос для отслеживания успешности __has_include, что делает код намного более подробным:

Код: Выделить всё

#ifdef __has_include
#if __has_include("header.h")
#include "header.h"
#define HAS_INCLUDE_HEADER_H
#endif
#endif

#ifndef HAS_INCLUDE_HEADER_H
#include "backup-header.h"
#endif

#undef HAS_INCLUDE_HEADER_H
Если мы не хотим этого делать, наивным решением было бы добавить еще один уровень косвенности:

Код: Выделить всё

// This part can be done once in a common header
#ifdef __has_include
#define MY_HAS_INCLUDE(Arg) __has_include(Arg)
#else
#define MY_HAS_INCLUDE(Arg) 0
#endif

#if MY_HAS_INCLUDE("header.h")
#include "header.h"
#else
#include "backup-header.h"
#endif
Это работает на многих (всех?) компиляторах, но, к сожалению, не соответствует стандартам, и недавний Clang предупредит об этом. Соответствующий отрывок из стандарта выглядит следующим образом:

Выражение, управляющее условным включением, должно быть целочисленным постоянным выражением, за исключением того, что идентификаторы (включая те, которые лексически идентичны ключевым словам) интерпретируются, как описано ниже, и оно может содержать ноль или более определенных макровыражений и/или имеет-включаемые-выражения и/или имеет-атрибутные-выражения как выражения унарного оператора.
...
Директивы #ifdef, #ifndef, #elifdef и #elifndef, а также определенный оператор условного включения должны обрабатывать __has_include и __has_cpp_attribute так, как если бы они были именами определенных макросов. Идентификаторы __has_include и __has_cpp_attribute не должны появляться ни в каком контексте, не упомянутом в этом подразделе.

https://timsong-cpp.github.io/cppwp/n4950/cpp.cond
(C23 имеет по существу идентичный пункт)
Насколько я понимаю, это говоря, что "

Код: Выделить всё

__has_include
можно использовать только в управляющем выражении #if/ или в качестве аргумента для define", но я немного не уверен в точных пределах.

Теперь, к сути моего вопроса. Я продумывал пару обходных путей и задавался вопросом, соответствуют ли какие-либо из них стандартам и будут ли они работать.
(A) Определение MY_HAS_INCLUDE как макроса без аргументов вместо одного это требует аргумента, который заглушает предупреждение Clang, но я сомневаюсь, что это действительно допустимо, поскольку мы все еще пишем фразу __has_include вне условия #if. Есть ли шанс, что это соответствует стандарту?

Код: Выделить всё

// This part can be done once in a common header
#ifdef __has_include
#define MY_HAS_INCLUDE __has_include
#else
#define HAS_INCLUDE_STUB(Arg) 0
#define MY_HAS_INCLUDE HAS_INCLUDE_STUB
#endif

#if MY_HAS_INCLUDE("header.h")
#include "header.h"
#else
#include "backup-header.h"
#endif
(B) Моя самая многообещающая идея заключается в следующем. Будет ли допустимо такое обертывание макроса вокруг всего выражения __has_include?

Код: Выделить всё

// This part can be done once in a common header
#ifdef __has_include
#define IF_HAS_INCLUDE_SUPPORTED(Arg) Arg
#else
#define IF_HAS_INCLUDE_SUPPORTED(Arg) 0
#endif

#if IF_HAS_INCLUDE_SUPPORTED(__has_include("header.h"))
#include "header.h"
#else
#include "backup-header.h"
#endif
(C) Если передача __has_include в макрос вообще недопустима, будет ли что-то подобное допустимым? В этом случае фраза __has_include передается в качестве аргумента макросу только в том случае, если она не определена. (Хотя это настолько некрасиво, что, вероятно, не стоит этого делать)

Код: Выделить всё

// This part can be done once in a common header
#ifdef __has_include
#define HAS_INCLUDE_TAKE_EMPTY_PAREN()
#define PRE_HAS_INCLUDE
#define POST_HAS_INCLUDE HAS_INCLUDE_TAKE_EMPTY_PAREN (
#else
#define HAS_INCLUDE_CONSUMER(Arg) 0
#define PRE_HAS_INCLUDE HAS_INCLUDE_CONSUMER (
#define POST_HAS_INCLUDE
#endif

#if PRE_HAS_INCLUDE __has_include("header.h") POST_HAS_INCLUDE )
#include "header.h"
#else
#include "backup-header.h"
#endif
РЕДАКТИРОВАТЬ: (D) Улучшенная версия, сочетающая два вышеуказанных обходных пути. В этом случае IF_HAS_INCLUDE_SUPPORTED(__has_include("header.h")) либо расширяется до 0, если __has_include не поддерживается, либо (__has_include("header.h")) в скобках, если это так. А в случае, когда поддерживается __has_include, он никогда не передается в качестве аргумента макросу. Это действительно? Конечно, так и должно быть.

Код: Выделить всё

// This part can be done once in a common header
#ifdef __has_include
#define IF_HAS_INCLUDE_SUPPORTED
#else
#define IF_HAS_INCLUDE_SUPPORTED(Arg) (0)
#endif

#if IF_HAS_INCLUDE_SUPPORTED(__has_include("header.h"))
#include "header.h"
#else
#include "backup-header.h"
#endif
Дополнение: я часто видел, как определяется __has_include сам, если он не определен:

Код: Выделить всё

// This part can be done once in a common header
#ifndef __has_include
#define __has_include(Arg) 0
#endif

#if __has_include("header.h")
#include "header.h"
#else
#include "backup-header.h"
#endif
Технически это является нарушением стандарта (я думаю), поскольку имена, начинающиеся с двойного подчеркивания, зарезервированы. Мне это также не нравится, потому что это затрудняет понимание того, что на самом деле происходит (поскольку кто-то, читающий более поздний код, может не осознавать, что мы сами определили __has_include), и это лишает более поздний код возможности проверить, действительно ли __has_include поддерживается с использованием define.


Подробнее здесь: https://stackoverflow.com/questions/798 ... d-is-it-va
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C++»