4

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.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
  • If you're OK with function macro_is_defined(HAVE_CXX11), then what's wrong with having macro MACRO_IS_DEFINED(HAVE_CXX11)? Then you can see how trivial it is to implement it. – Artem Tokmakov Aug 04 '13 at 22:17
  • @biocomp How would I implement such a macro? I'd need to use #ifdef to define MACRO_IS_DEFINED to true or false, but I can't make a single line expand into 5 lines of preprocessor directives – cfa45ca55111016ee9269f0a52e771 Aug 04 '13 at 22:20
  • 2
    Uhm, how are you planning to use `constexpr` without C++11 support? – catscradle Aug 04 '13 at 22:36
  • @catscradle Good point. I'm not. The C++11 support is just an example. Instead I could be checking for support of some library, or C++14 support in the future (while assuming C++11 and freely using constexpr). Or I could simply drop constexpr and the question remains relevant, limiting usage to realtime i.e. can't use with enable_if – cfa45ca55111016ee9269f0a52e771 Aug 04 '13 at 22:40
  • 1
    Ok, and how would you use `std::enable_if` in your `cbegin` example? – catscradle Aug 04 '13 at 22:53
  • @catscradle If I use constexpr then I can use enable_if like in the examples here: http://en.cppreference.com/w/cpp/types/enable_if. I could use enable_if on the return type of cbegin(). Without constexpr of course it's impossible – cfa45ca55111016ee9269f0a52e771 Aug 04 '13 at 23:06
  • @fr33domlover Well, have you tried it? Because I don't think it works the way you think it does... – catscradle Aug 04 '13 at 23:14
  • @catscradle No, but I trust the examples I linked to. Anyway it's just a specific example. I'm interested in generally being able to use the constexpr values, e.g. in boolean parameters of templates. So enable_if is just a specific example of the general idea – cfa45ca55111016ee9269f0a52e771 Aug 04 '13 at 23:18
  • @fr33domlover you're right, my bad. Can't really define macro with #ifdef inside. – Artem Tokmakov Aug 04 '13 at 23:36
  • @biocomp Indeed... check the edit, I'm considering to write a function-generator running at configure time. – cfa45ca55111016ee9269f0a52e771 Aug 04 '13 at 23:47
  • @catscradle This is how enable_if can be used with cbegin(), check the example in the first answer: http://stackoverflow.com/questions/11363822/compile-time-conditional-member-function-call-in-c – cfa45ca55111016ee9269f0a52e771 Aug 04 '13 at 23:48
  • 1
    Just wondering: is there any particular reason you don't want cbegin() and cend() in pre-C++11 code? It usually doesn't hurt to have them (at least until someone wants to change containers). – Nevin Aug 05 '13 at 17:32
  • @Nevin No, I decided to include them in my code even without C++11 :-) But the original question still stands – cfa45ca55111016ee9269f0a52e771 Aug 06 '13 at 10:18
  • If a macro is defined as nothing, it expands to nothing. If it is undefined, it expands as itself. What you need is an expression where a blank does one thing, and any other identifier does something else. Macros defined as an arbitrary string can be attacked elsewhere... – Yakk - Adam Nevraumont Aug 06 '13 at 19:31
  • 1
    I wrote a nuts-and-bolts sort of answer to the Q. and deleted it when I realized this. The autoconf macro that defines `HAVE_CXX`, or not, is `AX_CXX_COMPILE_STDCXX_11` and it defines `HAVE_CXX` if the compiler supports `-std=c++0x` or `-std=c++11` or the `gnu` variants. So if `HAVE_CXX` turns out not defined then you can't possibly do any `std::enable_if` SFINAEs based on whether or not the macro is defined because in that case the compiler doesn't have `std::enable_if` anyhow. – Mike Kinghan Aug 07 '13 at 07:52
  • 1
    @MikeKinghan You're right, but HAVE_CXX11 is a specific case. Assume I was testing for some other macro, not related to language features – cfa45ca55111016ee9269f0a52e771 Aug 07 '13 at 18:02
  • @fr33domlover OK, I'll generalize it and repost. – Mike Kinghan Aug 07 '13 at 22:29

4 Answers4

4

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.

Community
  • 1
  • 1
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
0

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).

zaufi
  • 6,811
  • 26
  • 34
  • 1
    You can "force" it to be a template function by adding a dummy template parameter. Check the first answer to this question: http://stackoverflow.com/questions/11363822/compile-time-conditional-member-function-call-in-c . I want to use constexpr functions (and not variables) because they hide the implementation detail and I can always change it without changing the interface. Anyway I need *something* constexpr. Whether it's a function/struct/variable doesn't really matter – cfa45ca55111016ee9269f0a52e771 Aug 05 '13 at 07:07
  • Anyway, how do I define a boolean which determines whether a given macro is defined? That is the goal – cfa45ca55111016ee9269f0a52e771 Aug 05 '13 at 07:23
0

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.

Walter
  • 44,150
  • 20
  • 113
  • 196
  • Indeed, the preprocessor is not powerful enough. But other solutions are possible, for example create a C++ header containing constexpr functions/variables in the same way Autotools create config.h – cfa45ca55111016ee9269f0a52e771 Aug 05 '13 at 09:31
0

In other words, what I want is to get a boolean indicating whether a given macro is defined.

This is possible using constexpr string comparison and a macro:

#define _eval(x) #x // extra round of macroexpansion (see https://stackoverflow.com/a/13074537/12808416)
#define is_defined(x) _is_defined(#x, _eval(x))

The form of the _is_defined() helper function depends on the C++ version:

C++20

constexpr string is (at long last!) part of the standard, although, as of 2023, compiler support is still weak. The code below only works with GCC ≥12 and the -std=c++2b option, for example.

#include <string>
using std::string;

template <typename T>
constexpr bool _is_defined(T s1, T s2) { return string(s1) != string(s2); }

C++17

#include <string_view>

constexpr bool _is_defined(const char s1[], const char s2[]) {
    return std::string_view(s1) != s2;
}

C++11

constexpr bool isequal(char const *one, char const *two) {
    return (*one && *two) ? (*one == *two && isequal(one + 1, two + 1)) : (!*one && !*two);
}
constexpr bool _is_defined(const char s1[], const char s2[]) { return !isequal(s1, s2); }

strcmp()

Some compilers provide this shortcut:

#include <cstring>
constexpr bool _is_defined(const char s1[], const char s2[]) {
     return strcmp(s1, s2); // true if unequal
}

is_defined() in use:

#define FOO
#define BAR 0 
#define BAZ 1 
#define INDIRECT FOO
#undef UNDEFINED
#define BAD BAD

static_assert(is_defined(FOO), "Empty but true");
static_assert(is_defined(BAR), "Defined true");
static_assert(is_defined(BAZ), "Defined false");
static_assert(is_defined(INDIRECT), "Expands to another token, still true");
static_assert(!is_defined(UNDEFINED), "False");
static_assert(!is_defined(BAD), "Extensionally indistinguishable from undefinedness");

It works by comparing the 'stringified' versions of x and whatever it macroexpands to. These will only be equivalent if x is not a defined preprocessor token (or if it expands to itself, as in the 'BAD' case).


  • 29 Mar 23 Edited to add other tests of C-string equality.
Dumaiu
  • 138
  • 1
  • 8