frx*_*x08 79 c conditional c-preprocessor
我必须在C中做这样的事情.它只有在我使用char时才有效,但我需要一个字符串.我怎样才能做到这一点?
#define USER "jack" // jack or queen
#if USER == "jack"
#define USER_VS "queen"
#elif USER == "queen"
#define USER_VS "jack"
#endif
Bri*_*ndy 61
我认为没有办法在预处理程序指令中完全进行可变长度字符串比较.您可以执行以下操作:
#define USER_JACK 1
#define USER_QUEEN 2
#define USER USER_JACK 
#if USER == USER_JACK
#define USER_VS USER_QUEEN
#elif USER == USER_QUEEN
#define USER_VS USER_JACK
#endif
或者您可以稍微重构代码并使用C代码.
Jes*_*olm 18
[更新:2018.05.03]
CAVEAT:并非所有编译器都以相同的方式实现C++ 11规范.下面的代码在我测试的编译器中工作,而许多评论者使用不同的编译器.
引自Shafik Yaghmour的回答:在编译时计算C字符串的长度.这真的是一个constexpr吗?
在编译时不保证会对常量表达式进行求值,我们只提供了草案C++标准5.19节中的非规范性引用.
[...]> [注意:在翻译过程中可以评估常量表达式.-end note]
这个词can在世界上有所不同.
所以,YMMV就此(或任何)答案而言constexpr,取决于编译器编写者对规范的解释.
[更新2016.01.31]
有些人不喜欢我之前的回答,因为它通过完成目标而不需要字符串比较来避免compile time string compare OP 的整个方面,这里有一个更详细的答案.
你不能!不在C98或C99中.甚至没有在C11.没有多少MACRO操作会改变这一点.
在中const-expression使用的定义#if不允许字符串.
它允许使用字符,因此如果您将自己限制为字符,则可以使用此字符:
#define JACK 'J'
#define QUEEN 'Q'
#define CHOICE JACK     // or QUEEN, your choice
#if 'J' == CHOICE
#define USER "jack"
#define USER_VS "queen"
#elif 'Q' == CHOICE
#define USER "queen"
#define USER_VS "jack"
#else
#define USER "anonymous1"
#define USER_VS "anonymous2"
#endif
#pragma message "USER    IS " USER
#pragma message "USER_VS IS " USER_VS
您可以!在C++ 11中.如果为比较定义编译时辅助函数.
// compares two strings in compile time constant fashion
constexpr int c_strcmp( char const* lhs, char const* rhs )
{
    return (('\0' == lhs[0]) && ('\0' == rhs[0])) ? 0
        :  (lhs[0] != rhs[0]) ? (lhs[0] - rhs[0])
        : c_strcmp( lhs+1, rhs+1 );
}
// some compilers may require ((int)lhs[0] - (int)rhs[0])
#define JACK "jack"
#define QUEEN "queen"
#define USER JACK       // or QUEEN, your choice
#if 0 == c_strcmp( USER, JACK )
#define USER_VS QUEEN
#elif 0 == c_strcmp( USER, QUEEN )
#define USER_VS JACK
#else
#define USER_VS "unknown"
#endif
#pragma message "USER    IS " USER
#pragma message "USER_VS IS " USER_VS
因此,最终,您将不得不改变实现为USER和选择最终字符串值的目标的方式USER_VS.
您不能在C99中进行编译时字符串比较,但您可以选择字符串进行编译时.
如果您真的必须进行编译时间比较,那么您需要更改为允许该功能的C++ 11或更新版本.
[原始回答]
尝试:
#define jack_VS queen
#define queen_VS jack
#define USER jack          // jack    or queen, your choice
#define USER_VS USER##_VS  // jack_VS or queen_VS
// stringify usage: S(USER) or S(USER_VS) when you need the string form.
#define S(U) S_(U)
#define S_(U) #U
更新:ANSI令牌粘贴有时不太明显.;-D
#在宏之前放置一个会导致它被更改为其值的字符串,而不是其裸值.
##在两个令牌之间加一个双引号会使它们连接成一个令牌.
因此,宏USER_VS具有扩展jack_VS或queen_VS取决于您设置的方式USER.
该字符串化宏S(...)使用宏间接这样命名宏的值被转换成字符串.而不是宏的名称.
因此USER##_VS变成jack_VS(或queen_VS),取决于你如何设置USER.
稍后,当字符串化宏用作S(USER_VS)的值USER_VS(jack_VS在本例中)被传递到间接步骤S_(jack_VS)其值(转换queen)成一个字符串"queen".
如果设置USER为queenthen,则最终结果为字符串"jack".
有关标记连接,请参阅:https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html
有关令牌字符串转换,请参阅:https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification
[更新2015.02.15纠正错字.]
以下对我有用。允许显示为符号宏值比较的内容。#error xxx只是看编译器的实际作用。用#define cat(a,b)a ## b替换cat定义会破坏事情。
#define cat(a,...) cat_impl(a, __VA_ARGS__)
#define cat_impl(a,...) a ## __VA_ARGS__
#define xUSER_jack 0
#define xUSER_queen 1
#define USER_VAL cat(xUSER_,USER)
#define USER jack // jack or queen
#if USER_VAL==xUSER_jack
  #error USER=jack
  #define USER_VS "queen"
#elif USER_VAL==xUSER_queen
  #error USER=queen
  #define USER_VS "jack"
#endif
我知道从技术上讲,这并没有回答OP的问题,但是在查看上面的答案时,我意识到(据我所知),没有任何简单的方法可以在预处理器中进行字符串比较而不诉诸一些“技巧”或其他编译器特定的魔法。因此,在重新考虑我的情况时,我意识到实际上只会有一组固定的字符串您想要/可以比较,因为预处理器无论如何都必须使用静态字符串。因此,能够与代码中类似“字符串”的东西进行比较更像是一种风格。因此,我决定添加具有类似于字符串语法的定义(在读取它时),但只是针对整数的定义,这看起来就像其他人建议的那样。例如:
#if USER == USER_JACK
  // do something
#elif USER == USER_QUEEN
  // do something else
#elif USER == USER_KING
  // do something completely different
#else
  // abort abort
#end
所以现在的问题只是适当地设置定义。
作为一个更具体的示例,我最初想要进行字符串比较,以便在使用 Cereal 序列化库时可以指定默认存档类型。在 Cereal 中,有 3 种有效的存档类型:JSON、XML 和 Binary,我希望用户能够将它们作为 CMake 中的字符串变量输入。我仍然使之成为可能(并且还使用 CMake 的 CACHE STRINGS 属性来约束变量),但随后将字符串转换为整数,然后再将其作为编译器定义传递。(我提前道歉,因为我知道这是以 CMake 为中心的,并且这不是原始问题的一部分。)
使用 CMake 实现自动化,在 CMakeLists.txt 文件中,我包含以下 SetupCereal.cmake 脚本:
set( CEREAL_DIR "" CACHE PATH "Path to Cereal installation" )
set( CEREAL_INCLUDE_DIR ${CEREAL_DIR}/include )
# Set up the  user input variable and constrain to valid values
set( CEREAL_ARCHIVE_DEFAULT_TYPE "JSON"
    CACHE STRING
    "Default Archive type to use for Cereal serialization"
)
set_property( CACHE CEREAL_ARCHIVE_DEFAULT_TYPE
    PROPERTY STRINGS JSON XML BINARY
)
# Convert the string to integer for preprocessor comparison
if ( "${CEREAL_ARCHIVE_DEFAULT_TYPE}" STREQUAL "JSON")
  set( CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE 0 )
elseif( "${CEREAL_ARCHIVE_DEFAULT_TYPE}" STREQUAL "XML" )
  set( CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE 1 )
elseif( "${CEREAL_ARCHIVE_DEFAULT_TYPE}" STREQUAL "BINARY" )
  set( CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE 2 )
endif()
# Setup the corresponding preprocessor definitions
set( CEREAL_DEFINES
    -DCEREAL_ARCHIVE_JSON=0
    -DCEREAL_ARCHIVE_XML=1
    -DCEREAL_ARCHIVE_BINARY=2
    -DCEREAL_ARCHIVE_DEFAULT_TYPE=${CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE}
)
然后我制作了一个附带的 CerealArchive.hpp 标头,如下所示:
#pragma once
#if CEREAL_ARCHIVE_DEFAULT_TYPE == CEREAL_ARCHIVE_JSON
#  include <cereal/archives/json.hpp>
namespace cereal
{
  using DefaultOutputArchive = JSONOutputArchive;
  using DefaultInputArchive  = JSONInputArchive;
}
#elif CEREAL_ARCHIVE_DEFAULT_TYPE == CEREAL_ARCHIVE_XML
#  include <cereal/archives/xml.hpp>
namespace cereal {
  using DefaultOutputArchive = XMLOutputArchive;
  using DefaultInputArchive  = XMLInputArchive;
} // namespace cereal
#elif CEREAL_ARCHIVE_DEFAULT_TYPE == CEREAL_ARCHIVE_BINARY
#  include <cereal/archives/binary.hpp>
namespace cereal
{
  using DefaultOutputArchive = BinaryOutputArchive;
  using DefaultInputArchive  = BinaryInputArchive;
}
#endif // CEREAL_ARCHIVE_DEFAULT_TYPE
然后客户端代码如下所示:
set( CEREAL_DIR "" CACHE PATH "Path to Cereal installation" )
set( CEREAL_INCLUDE_DIR ${CEREAL_DIR}/include )
# Set up the  user input variable and constrain to valid values
set( CEREAL_ARCHIVE_DEFAULT_TYPE "JSON"
    CACHE STRING
    "Default Archive type to use for Cereal serialization"
)
set_property( CACHE CEREAL_ARCHIVE_DEFAULT_TYPE
    PROPERTY STRINGS JSON XML BINARY
)
# Convert the string to integer for preprocessor comparison
if ( "${CEREAL_ARCHIVE_DEFAULT_TYPE}" STREQUAL "JSON")
  set( CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE 0 )
elseif( "${CEREAL_ARCHIVE_DEFAULT_TYPE}" STREQUAL "XML" )
  set( CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE 1 )
elseif( "${CEREAL_ARCHIVE_DEFAULT_TYPE}" STREQUAL "BINARY" )
  set( CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE 2 )
endif()
# Setup the corresponding preprocessor definitions
set( CEREAL_DEFINES
    -DCEREAL_ARCHIVE_JSON=0
    -DCEREAL_ARCHIVE_XML=1
    -DCEREAL_ARCHIVE_BINARY=2
    -DCEREAL_ARCHIVE_DEFAULT_TYPE=${CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE}
)
然后,开发人员可以选择默认的 Archive 类型作为 CMake 字符串变量(当然随后是重新编译)。
因此,虽然从技术上讲,这个解决方案不是比较字符串,但从语法上讲,它的行为/看起来是相同的。
我还认为,SetupCereal.cmake 可以进一步概括为将设置封装在函数中,因此它可以用于您想要定义类似类型的定义的其他情况。