将 C++ 预处理器宏替换为可以初始化结构的宏

z32*_*7ul 5 c++

我正在使用 WinAPI 的CreateDialogIndirect函数,该函数对第二个参数指向的DLGTEMPLATEDLGTEMPLATEEX结构有一些要求。我的代码运行良好,但是,我想摆脱宏#define

我创建了一个简化的示例来重点关注宏。这是一个带有宏的工作程序,可以编译它,并输出预期的内容:

#include <iostream>

int wmain()
{
#define TITLE L"Title"
    struct {
        wchar_t title[ sizeof( TITLE ) / sizeof( TITLE[ 0 ] ) ];
        int font_size;
    } s = {
        TITLE,
        12,
    };

    std::wcout
        << L"s.title = "     << s.title     << std::endl
        << L"s.font_size = " << s.font_size << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

在 Visual Studio 2022 中,我在宏处看到三个点#define,并且可以阅读以下工具提示:

  • 宏可以转换为 constexpr
  • 显示潜在的修复
  • 将宏转换为 constexpr

我希望看到它转换为避免宏的东西,所以我点击它,代码更改为:

#include <iostream>

int wmain()
{
constexpr auto TITLE = L"Title";
    struct {
        wchar_t title[ sizeof( TITLE ) / sizeof( TITLE[ 0 ] ) ];
        int font_size;
    } s = {
        TITLE,
        12,
    };

    std::wcout
        << L"s.title = "     << s.title     << std::endl
        << L"s.font_size = " << s.font_size << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果我按 F7 进行编译,则会收到以下错误消息:

  • example.cpp(10,9): 错误 C2440: '初始化': 无法从 'const wchar_t *const ' 转换为 'wchar_t'

我不想输入L"Title"两次,也不想手动计算字符串的长度。那么,什么可以很好地替代宏,它既可以初始化结构体中的数组struct,又可以确定结构体中数组的大小?

JaM*_*MiT 2

模板是宏使用的相当常见的替代品。既然你不想手动计算字符串的长度(我不怪你),那么让我们为模板参数设置一个长度(数组长度,而不是字符串长度)。您的匿名者struct可能成为模板化实体的候选者,因为每次更改标题长度时您最终都会得到不同的类型。

到模板的实际转换非常简单(考虑到问题的级别),我不会在这里详细介绍细节。当您编写构造函数时,最有趣的部分就出现了。通过使用参数列表中的模板参数,编译器将能够推断出它。这为您提供了您似乎正在寻找的易用性,尽管需要进行一些额外的设置。

警告:通过构造函数的参数推导模板参数是 C++17 的功能。任何使用 C++11 或 14 的人(希望不是很多人)都可以通过编写一个构造并返回结构体的单独函数模板来获得类似的结果。我将把它作为练习。

#include <iostream>
#include <cstring>    // For memcpy


template <size_t N>
struct DlgTemplate {
    wchar_t title[N];
    int font_size;

    // Constructor that will deduce `N`.
    // For those not familiar with this syntax: the incoming title_ is a
    // reference to an array with exactly N elements.
    DlgTemplate(const wchar_t (&title_)[N], int font_size_) :
        font_size(font_size_)
    {
        // Copy the title.
        std::memcpy(title, title_, sizeof(title));
    }

    // Reminder: N is the array length, which includes the null terminator.
    //           The string length is N-1.
};


int main()
{
    // As of C++17, the template argument can be deduced here.
    auto s = DlgTemplate(L"Title", 12);

    std::wcout
        << L"s.title = "     << s.title     << std::endl
        << L"s.font_size = " << s.font_size << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

下一个考虑的可能是如何处理所有被仁慈地排除在外的领域。一种选择是向构造函数添加更多参数来处理所有字段。然而,为了可读性,我可能倾向于删除font_size_作为构造函数参数(记住标记构造函数explicit),然后在构造后设置每个字段。以下是我对初始化的想法。

    auto s = DlgTemplate(L"Title");
    s.font_size = 12;
    //s.other_field = value;
    // etc.
Run Code Online (Sandbox Code Playgroud)

这个问题有一个重要的细节需要注意。Visual Studio 实现的“潜在修复”导致长度信息丢失。

constexpr auto TITLE = L"Title";
Run Code Online (Sandbox Code Playgroud)

定义TITLE为一个指针(指向const wchar_t)。它的大小是固定的,与标题的长度无关。计算sizeof( TITLE ) / sizeof( TITLE[ 0 ] )不会给出标题的长度,这TITLE不适用于模板。

constexpr wchar_t TITLE[] = L"Title";
Run Code Online (Sandbox Code Playgroud)

定义TITLE为一个数组,长度信息保留为类型的一部分。这TITLE可以与模板一起使用。