3

在我的 C++ 项目(我使用 autotools)中,我有一个带有begin()和成员函数的类,并且当且仅当支持 C++11 时end(),我想选择性地包含cbegin()and 。cend()

我使用 Autoconf 存档中的 M4 文件测试 C++11 支持。如果支持,它是一个定义 HAVE_CXX11 的宏,否则它不定义它。

做我想做的C方式是:

#ifdef HAVE_CXX11
    const_iterator cbegin () const;
    const_iterator cend () const;
#endif

但我想以 C++ 的方式做一些事情。在这种情况下,我可以使用std::enable_if来选择性地允许cbegincend。像这样:

#ifdef HAVE_CXX11
#define MY_HAVE_CXX11 true
#else
#define MY_HAVE_CXX11 false

constexpr bool have_cxx11 ()
{
    return MY_HAVE_CXX11;
}

/* Now use have_cxx11() with std::enable_if */

这适用于单个特定宏,但如果我想为任何给定的宏自动化它怎么办?换句话说,我想要的是获得一个布尔值,指示是否定义了给定的宏。

我看到两个选项:

  1. 每个宏都有一个特定的功能
  2. 拥有单一功能

示例 1:

当 autoconf 定义其变量 HAVE_CXX11 时,它还将定义 have_cxx11() 以返回 true 或 false,具体取决于 HAVE_CXX11 autoconf 变量(不是宏常量)是定义为 0 还是 1。

示例 2:

可以是一个macro_is_defined()返回布尔值的函数,例如当我构建我的 C++11 项目时macro_is_defined(HAVE_CXX11)将返回。true

我试图找到一种在纯 C++ 中实现这些想法的方法,但没有找到。我不得不将一行代码扩展成一个预处理器指令块,而 IIRC 是不可能的。我应该怎么办?尝试像我尝试的 C++ 方式做事是个好主意,还是太过分了?

编辑:

autoheader#undef为所有宏创建 s,即使是那些最终被注释掉的宏。所以我可以编写一个脚本来扫描 config.h 并为每个宏生成一个 constexpr 函数。问题是它应该在构建过程中插入到哪里以及如何调用它。

4

3 回答 3

4

autoheader 为所有宏创建#undefs,即使是那些最终被注释掉的宏。所以我可以编写一个脚本来扫描 config.h 并为每个宏生成一个 constexpr 函数。问题是它应该在构建过程中插入到哪里以及如何调用它。

如果您满意(我认为您应该满意)您无法纯粹在 C++ 中实现目标,并且需要根据报价提供一些自定义构建支持,那么您可能会看到一个不存在的复杂情况。

我不认为您需要一个函数,无论是每个HAVE_XXXX宏一个或一个所有HAVE_XXXX宏,都可以告诉您是否定义了给定的宏。我的意思是我看不出你为什么应该从思考如何开始:

#ifdef HAVE_XXXX
#define MY_HAVE_XXXX true
#else
#define MY_HAVE_XXXX false
#endif

constexpr bool have_xxxx ()
{
    return MY_HAVE_XXXX;
}

可以为所有HAVE_XXXX宏自动化。您可以在.h文件中或将其替换.cpp为:

#ifdef HAVE_XXXX
bool const have_XXXX = true;
#else
bool const have_XXXX = false;
#endif

在 C++ 中,const对象默认具有内部链接,因此即使在头文件中,这些定义也不会在链接时出现多个定义错误的风险。

鉴于此,定制的构建解决方案将执行一个脚本来解析 config.h并写出一个头文件,比如config_aux.h包含config.h 和包含:

#ifdef HAVE_XXXX
bool const have_xxxx = true;
#else
bool const have_xxxx = false;
#endif

对于每个HAVE_XXXX.

您确保将config_aux.h其构建为其他所有内容的先决条件,然后对于您的所有编译,您确保config_aux.h编译器将其预先包含在每个翻译单元中。这样做的方法是通过 g++ 选项:

-include config_aux.h

看到这个答案

另一方面,我认为你在如何实际使用have_xxxx来做你想做的事情方面走错了路。

在您已链接到此答案的评论中,表明您将enable-if-SFINAE 技术视为静态启用或禁用成员函数的模型,方法是使其成为具有两个 SFINAE 替代方案的模板成员函数:一个将由模板解析选择什么时候have_xxxx是真的,什么时候会被选择的那个have_xxxx是假的。

这实际上不是您要求的模型。它展示了如何在 每个模板参数适用时执行业务的一个实现与无操作的替代实现之间 SFINAE 成员函数。

但是,如果调用了静态禁用的成员函数,您不希望您的程序什么都不做。您希望这样的调用 导致编译错误。例如,当为 false 时,您不希望调用T::cbegin() 成为无操作HAVE_CXX11:您希望调用成为编译时错误。

所以你不需要SFINAE,但你确实需要成员函数成为模板成员函数,因为模板解析是在同一个类中静态启用或禁用它的唯一机制(不包括旧#ifdef方式)。

以下程序似乎说明了明显的解决方案:

#include <type_traits>

#define HAVE_XXXX 1 // Pretend we get this from autoconf

// Pretend we get this from config_aux.h
#ifdef HAVE_XXXX
bool const have_xxxx = true;
#else
bool const have_xxxx = false;
#endif

struct X
{
    void a(){}

    template<typename R = typename std::enable_if<have_xxxx,void>::type>
    R b() {}
};


int main()
{
    X x;
    // x.b();
    return 0;
}

HAVE_XXXX定义后编译,同时实现X::aX::b。它可以X::b. 但是如果HAVE_XXXX没有定义,那么任何调用X::b 都需要实例化该成员函数,std::enable_if<false,void> 并且不会编译。

HAVE_XXXX但只需编辑程序以便未定义并重建它。

构建失败:

错误:“struct std::enable_if”中的“type”未命名类型

即使程序仍然没有调用X::b. HAVE_XXXX除非已定义,否则您根本无法构建程序。

这里的问题是编译器总是可以have_xxxx在不求助于模板解析的情况下进行评估。确实如此;所以它总是会发现那个错误。

为了防止这种情况,您需要 的条件enable_if取决于函数的模板参数,因此它只会在模板解析中被评估,但仍然始终为真 [假],如果它被评估,只要have_cxxx为真[错误的]。任何具有这种效果的东西都会起作用,并且条件如下:

!std::is_same<R,R>::value || have_xxxx

可能会想到。但:

template<
    typename R = 
    typename std::enable_if<(!std::is_same<R,R>::value || have_xxxx),void>::type
>
R b() {}

不会以任何方式编译,因为它尝试使用模板参数R来定义自身的默认类型。

但是,就像std::enable_if这个无关紧要的障碍一样,为什么要求助于它呢?一个static_assert合适的条件在体内的X::b意志做得很好。在诊断上,它会做得更好。所以改为定义X::b如下:

    template<typename R = void>
    R b() {
        static_assert(!std::is_same<R,R>::value || have_xxxx,
            "X::b is unimplemented without XXXX support");
    }

现在,程序将编译是否HAVE_XXXX已定义,当它被定义时,您还可以取消注释对X::b. 但是如果HAVE_XXXX未定义并且您还取消了对 的调用的注释X::b,则成员函数将被实例化;R 被定义;的条件 static_assert被评估,发现错误,然后static_assert 火灾。这就是你想要的结果。

于 2013-08-07T22:42:18.327 回答
0

以便携式方式进行测试C++11如下

#if __cplusplus >= 201103L

  /* C++11 stuff */

#endif

而不是使用特定于环境的宏,例如HAVE_CXX11.

我不认为你可以自动创建一个为你的预处理器宏提供语言接口的方法,至少如果条件是 form#ifdef MACRO则不能,因为预处理器对于这种事情来说不够强大。

于 2013-08-05T09:27:17.453 回答
0

看来您误解了何时std::enable_if可以使用。要使用它,您的函数(或类)必须是模板!因此,您不能将此技巧用于非模板函数。我认为cbegin()并且cend()不需要成为模板......

实际上,唯一通用的解决方案是以某种方式使用预处理器。并且通常像您在第一个代码片段中提到的那样使用它。所以,IMO,你在这里过度设计了:)

顺便说一句,不需要定义constexpr函数......你真正需要的是从std::true_typestd::false_type(或更通用的方式只是使用std::integral_constant<bool, (some-boolean-expression-here)>)定义某种类型(结构),所以它可以用作enable_if条件(通过访问嵌套value成员)。

于 2013-08-05T00:50:43.757 回答