c++c++11c-preprocessorautoconfconstexpr

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, e.g. 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 #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.


Solution

  • 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. E.g. 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.