【问题标题】:Stringize operator on argument with spaces使用空格对参数进行字符串化操作
【发布时间】:2020-07-02 18:26:03
【问题描述】:

我有

#define ARG(TEXT , REPLACEMENT) replace(#TEXT, REPLACEMENT)

所以

QString str= QString("%ONE, %TWO").ARG(%ONE, "1").ARG(%TWO, "2");

变成

str= QString("%ONE, %TWO").replace("%ONE", "1").replace("%TWO", "2");
//str = "1, 2"

问题在于 VS2019 在格式化代码时 (Edit.FormatSelection) 将 % 符号解释为运算符并添加空格

QString str= QString("%ONE, %TWO").ARG(% ONE, "1").ARG(% TWO, "2");

(我认为这是 VS 中的错误)。代码编译时没有警告。

当我正在处理一些具有此“功能”传播的古老代码时,我担心会自动格式化包含此“功能”的文本并破坏功能。

有没有办法在编译时检测到带有空格的宏的此类参数?

【问题讨论】:

  • 我对你的宏的第一个想法是“为什么用户必须引用第二个参数而不是第一个”?如果你通过让用户写.ARG("%ONE", "1") 来解决这个问题,问题就会消失(并且宏会更加一致)。不是您问题的答案,而是一种可能的替代方法...
  • 似乎格式化程序看到一个运算符(%)后跟一个标识符(ONE),因此它正在添加空间。从上下文来看,它并没有太多考虑。
  • 尽管第一次出现,但似乎这个问题是关于 VS IDE 的一个特性,与编译或语言无关?
  • 是的,我认为您不能说这是格式化程序中的错误。它怎么知道%ONE 不是表达式的一部分?我大多同意让用户自动写.ARG("%ONE", "1") or to add the %` 以便用户写.ARG(ONE, "1") 会更干净。但由于这可能是遗留代码,我猜你所拥有的就是你所拥有的。我添加了一个非常老套的答案。

标签: c++ c-preprocessor visual-studio-2019


【解决方案1】:

有没有办法在编译时检测到带有空格的宏的此类参数?

我会这样做:

#define ARG(TEXT, REPLACEMENT) \
    replace([]{ \
        static constexpr char x[] = #TEXT; \
        static_assert(x[0] == '%' && x[1] != ' '); \
        return x; \
    }(), REPLACEMENT)

【讨论】:

    【解决方案2】:

    显然,在未来十年的某个时间,C++ 将提供更好的解决方案,而且确实可能​​有一个比我在下面提供的解决方案更简洁的解决方案,但它可能是一个开始的地方。

    这个版本使用 Boost Preprocessor 库进行重复,如果 C++ 允许字符串文字作为模板参数,那么使用模板编写本可以很简单,这个功能还没有成为动机的标准,我只能猜测。所以它实际上并没有测试参数是否没有空格;相反,它测试前 64 个字符中没有空格(其中 64 几乎是一个完全任意的数字,可以根据您的需要进行更改)。我使用了 Boost 预处理器库;如果出于某种原因不想使用 Boost,您可以使用自己的特殊用途宏来执行此操作。

    #include <boost/preprocessor/repetition/repeat.hpp>
    #define NO_SPACE_AT_N(z, N, s) && (N >= sizeof(s) || s[N] != ' ')
    #define NO_SPACE(s) true BOOST_PP_REPEAT(64, NO_SPACE_AT_N, s) 
    // Arbitrary constant, change as needed---^
    
    // Produce a compile time error if there's a space.
    template<bool ok> struct NoSpace {
        const char* operator()(const char* s) {
            static_assert(ok, "Unexpected space");
            return s;
        }
    };
    
    #define ARG(TEXT, REPL) replace(NoSpace<NO_SPACE(#TEXT)>()(#TEXT), REPL)
    

    (在gcc.godbolt 上测试。)

    【讨论】:

      【解决方案3】:

      如果问题是在ARG 的第一个参数包含空格时产生编译错误,我设法让它工作:

      #include <cstdlib>
      
      template<size_t N>
      constexpr int string_validate( const char (&s)[N] )
      {
          for (int i = 0; i < N; ++i)
              if ( s[i] == ' ' )
                  return 0;
          return 1;
      }
      
      template<int N> void assert_const() { static_assert(N, "string validation failed"); }
      
      int replace(char const *, char const *) { return 0; }   // dummy for example
      #define ARG(TEXT , REPLACEMENT) replace((assert_const<string_validate(#TEXT)>(), #TEXT), REPLACEMENT)
      
      int main()
      {
          auto b = ARG(%TWO, "2");
          auto a = ARG(% ONE, "1");   // causes assertion failure
      }
      

      无疑还有更短的方法。在 C++20 之前,您不能在模板参数中使用字符串文字,因此 constexpr 函数从字符串文字生成整数,然后我们可以在编译时通过将其用作模板参数来检查整数。

      【讨论】:

        【解决方案4】:

        这不太可能。

        Visual Studio 在源代码上工作,无需先运行预处理器,也无需执行相当困难的计算来确定预处理器是否会从根本上改变其格式。

        此外,人们实际上不再以这种方式使用宏,或者不应该(我们有廉价的函数!)。

        所以这并不是格式化功能所期望的。

        如果你可以修改代码,让用户写.ARG("%ONE", "1"),那么问题不仅会消失,而且会更加一致。

        否则,您将不得不坚持手动格式化代码。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-04-05
          • 1970-01-01
          • 1970-01-01
          • 2015-05-31
          相关资源
          最近更新 更多