简体   繁体   中英

C++ constexpr function to test preprocessor macros

In my C++ project (I use autotools) I have a class with begin() and end() member functions, and I want to optionally include cbegin() and cend() , if and only if C++11 is supported.

I test C++11 support using an M4 file from the Autoconf Archive. It's a macro which defines HAVE_CXX11 if supported, otherwise it doesn't define it.

The C way to do what I want is:

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

But I want to do some things in the C++ way. In this case I can use std::enable_if to optionally allow cbegin and cend . Like this:

#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 */

This works fine with a single specific macro, but what if I want to automate it for any given macro? In other words, what I want is to get a boolean indicating whether a given macro is defined.

I see two options:

  1. Have a specific function for each macro
  2. Have a single function

Example for 1:

When autoconf defines its variable HAVE_CXX11, it will also define have_cxx11() to return true or false depending on whether the HAVE_CXX11 autoconf variable (not the macro constant) is defined to 0 or 1.

Example for 2:

The can be a function macro_is_defined() which returns a boolean, eg macro_is_defined(HAVE_CXX11) will return true when I build my C++11 project.

I tried to find a way to implement these ideas in pure C++, but found none. I had to make a single line of code expand into a block of preprocessor directives, which IIRC is impossible. What should I do? And is it a good idea to try doing things the C++ way like I try, or it's too much?

EDIT:

autoheader creates #undef s for all macros, even the ones which eventually get commented out. So I could write a script which scans config.h and generates a constexpr function for each macro. The question is where it should be inserted in the build process and how it's called.

autoheader creates #undefs for all macros, even the ones which eventually get commented out. So I could write a script which scans config.h and generates a constexpr function for each macro. The question is where it should be inserted in the build process and how it's called.

If you are satisfied (as I think your should be) that you can't accomplish the goal purely in C++ and will need some custom build support as per quote, then perhaps you are seeing one complication that isn't there.

I don't see that you need a function , either one per HAVE_XXXX -macro or one for all HAVE_XXXX -macros, to tell you whether a given macro is defined. I mean I see no reason why you should start by pondering how this:

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

constexpr bool have_xxxx ()
{
    return MY_HAVE_XXXX;
}

can be automated for all HAVE_XXXX -macros. You could replace that either in a .h file or a .cpp with:

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

In C++, const objects have internal linkage by default, so even in a header file these definitions run no risk of multiple definition errors at link time.

In that light, a customized build solution would execute a script to parse the config.h and write out a header file, say config_aux.h that includes config.h and contains:

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

for each HAVE_XXXX .

You ensure that config_aux.h is built as a prerequisite of everything else and then for all your compiles you ensure that config_aux.h is pre-included by the compiler in every translation unit. The way to do that is to pass g++ the option:

-include config_aux.h

See this answer

On the other hand, I think you're on the wrong track as to how you would actually use have_xxxx to do what you want.

In a comment you have linked to this answer , indicating that you see that enable-if -SFINAE technique as a model for statically enabling or disabling a member function, by making it a template member function with two SFINAE alternatives: one that would be chosen by template resolution when have_xxxx is true, and one that will be chosen when have_xxxx is false.

That's not actually a model for your requirement. It shows how to SFINAE a member function between one implementation that does the business when applicable per template parameter and alternative implementation that is a no-op.

But you don't want your program to do nothing if a statically disabled member function gets called. You want such a call to cause a compilation error . Eg you don't want a call to T::cbegin() to be a no-op when HAVE_CXX11 is false: you want the call to be a compiletime error.

So you don't need SFINAE, but you do need the member function to become a template member function, because template resolution is the only mechanism for statically enabling or disabling it within the same class (not counting the old #ifdef way).

It might seem that the obvious solution is illustrated in the following program:

#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;
}

With HAVE_XXXX defined it compiles, implementing both X::a and X::b . It could make the commented-out call to X::b . But if HAVE_XXXX were not defined then any call to X::b would require instantation of that member function with std::enable_if<false,void> and that would not compile.

But just edit the program so that HAVE_XXXX is not defined and rebuild it.

The build fails:

error: 'type' in 'struct std::enable_if' does not name a type

even though the program still makes no calls to X::b . You can't build the program at all unless HAVE_XXXX is defined.

The problem here is that the compiler can always evaluate have_xxxx without resort to template resolution. So it does; so it always discovers that error.

To prevent this, you would need the condition of the enable_if to depend on a template parameter of the function, so it will only be evaluated in template resolution, but still always to be true [false], if it is evaluated, as long have_cxxx is true [false]. Anything to that effect will do and a condition like:

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

might come to mind. But:

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

will not compile any which way, for the elementary reason that it attempts to use the template parameter R to define the default type of itself.

But as std::enable_if has this irrelevant snag, why resort to it at all? A static_assert of a suitable condition within the body of X::b will do just was well. Diagnostically, it will do better. So instead define X::b like:

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

Now, the program will compile whether HAVE_XXXX is defined or not and when it is defined you can also uncomment the call to X::b . But if HAVE_XXXX is not defined, and you also uncomment the call to X::b , then the member function gets instantiated; R is defined; the condition of the static_assert is evaluated, found false, and the static_assert fires. This is the outcome you want.

it seems you misunderstood when std::enable_if can be used. to use it, your function (or class) must be a template! so, you can't use this trick for non template functions. and I think cbegin() and cend() do not needed to be templates...

actually, the only generic solution is to use preprocessor somehow. and usually it is used like you mention in your first code snippet. so, IMO, you over-engineering here :)

and btw, it is not needed to define constexpr functions... all that you really need is to define some type (struct) public from std::true_type or std::false_type (or more generic way just use std::integral_constant<bool, (some-boolean-expression-here)> ), so it can be used as enable_if condition (by accessing a nested value member).

Testing for C++11 in portable way is done as follows

#if __cplusplus >= 201103L

  /* C++11 stuff */

#endif

rather than using environment-specific macros such as HAVE_CXX11 .

I don't think that you can automate the creation of a method that provides a language interface to your preprocessor macro, at least not if the condition is of the form #ifdef MACRO , because the pre-processor is not powerful enough for this sort of thing.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM