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:
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 #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. 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.