Conditional compilation oddity

  • Thread starter Norman J. Goldstein
  • Start date
N

Norman J. Goldstein

The following program compiles and links.
No warnings The output is wrong, stating:

value= 1
value NOT 1


//////////////////////////////////////////////
#include <iostream>
using namespace std;

static constexpr int value = 1;

int main( int argc, char* argv[] )
{
cout << "value= "
<< value
<< endl;

static_assert( value == 1, "Value_Not_1" );

#if value == 1
cout << "value 1" << endl;
#else
cout << "value NOT 1" << endl;
#endif

return 0;
}// main
/////////////////////////////////////////////////

Why is value not used in the conditional compilation?
(The static_assert is happy.)

I am using
gcc version 4.7.2 20121109 (Red Hat 4.7.2-8) (GCC)
pn
Linux 3.9.11-200.fc18.i686.PAE #1 SMP
 
I

Ian Collins

Norman said:
The following program compiles and links.
No warnings The output is wrong, stating:

value= 1
value NOT 1


//////////////////////////////////////////////
#include <iostream>
using namespace std;

static constexpr int value = 1;

int main( int argc, char* argv[] )
{
cout << "value= "
<< value
<< endl;

static_assert( value == 1, "Value_Not_1" );

#if value == 1
cout << "value 1" << endl;
#else
cout << "value NOT 1" << endl;
#endif

return 0;
}// main
/////////////////////////////////////////////////

Why is value not used in the conditional compilation?
(The static_assert is happy.)

Because the conditional part is handled by the preprocessor, not the
compiler. The symbol "value" is unknown to the preprocessor so the first
condition isn't compiled.
 
N

Norman J. Goldstein

Norman said:
The following program compiles and links.
No warnings The output is wrong, stating:

value= 1
value NOT 1


//////////////////////////////////////////////
#include <iostream>
using namespace std;

static constexpr int value = 1;

int main( int argc, char* argv[] )
{
cout << "value= "
<< value
<< endl;

static_assert( value == 1, "Value_Not_1" );

#if value == 1
cout << "value 1" << endl;
#else
cout << "value NOT 1" << endl;
#endif

return 0;
}// main
/////////////////////////////////////////////////

Why is value not used in the conditional compilation?
(The static_assert is happy.)

Because the conditional part is handled by the preprocessor, not the
compiler. The symbol "value" is unknown to the preprocessor so the first
condition isn't compiled.
Thanks, Ian. I checked using "X" instead of "value", with the same
result. I think some such capability would be very helpful. The SFINAE
is well and good in template matching, but is awkward to take advantage
of in my particular circumstance. The static_assert mechanism shows
that such conditional compilation is "almost" provided by C++11.
 
I

Ian Collins

Norman said:
Thanks, Ian. I checked using "X" instead of "value", with the same
result. I think some such capability would be very helpful. The SFINAE
is well and good in template matching, but is awkward to take advantage
of in my particular circumstance. The static_assert mechanism shows
that such conditional compilation is "almost" provided by C++11.

I think conditional compilation is overrated. There are usually
(admittedly not always!) more elegant solutions that don't involve the
preprocessor.

You could achieve your original goal by resorting to a #define rather
than a constant for value, but it's still any ugly kludge.
 
Ö

Öö Tiib

The following program compiles and links.
No warnings The output is wrong, stating:

value= 1
value NOT 1

//////////////////////////////////////////////
#include <iostream>
using namespace std;

static constexpr int value = 1;

int main( int argc, char* argv[] )
{
cout << "value= "
<< value
<< endl;

static_assert( value == 1, "Value_Not_1" );

#if value == 1
cout << "value 1" << endl;
#else
cout << "value NOT 1" << endl;
#endif

I am not sure if above is undefined behavior, but I know no one who
would accept it in review. You compare undefined macro 'value' with
1 here.
return 0;
}// main
/////////////////////////////////////////////////

Why is value not used in the conditional compilation?
(The static_assert is happy.)

Because 'value' is not macro. 'static_assert' is not done by
preprocessor, so it works. All compilers I know of (that implement
'static_assert') optimize that if and the contents of else part
away from resulting binary when you write it like that:

//////////////////////////////////////////////
#include <iostream>
static constexpr int value = 1;

int main()
{
std::cout << "value= " << value << std::endl;

static_assert( value == 1, "Value_Not_1" );

if (value == 1) // <- some compilers may issue silly warnings
// about condition being always true
{
std::cout << "value 1" << std::endl;
}
else
{
// <- some compilers may issue silly warnings
// about unreachable code
std::cout << "value NOT 1" << std::endl;
}
}
/////////////////////////////////////////////////

So you need to be more specific ... why you need conditional
compiling here? It only makes code harder to read.
 
N

Norman J. Goldstein

... All compilers I know of (that implement
'static_assert') optimize that if and the contents of else part
away from resulting binary when you write it like that:

//////////////////////////////////////////////
#include <iostream>
static constexpr int value = 1;

int main()
{
std::cout << "value= " << value << std::endl;

static_assert( value == 1, "Value_Not_1" );

if (value == 1) // <- some compilers may issue silly warnings
// about condition being always true
{
std::cout << "value 1" << std::endl;
}
else
{
// <- some compilers may issue silly warnings
// about unreachable code
std::cout << "value NOT 1" << std::endl;
}
}
/////////////////////////////////////////////////

So you need to be more specific ... why you need conditional
compiling here? It only makes code harder to read.

Below is source code that illustrates how conditional compilation
would have been helpful for readability. The template method
T* NewGuy( T* ptr )
is to return an independent T ptr, either bay calling new T, or,
preferably, by calling the method ptr->newGuy(). When ptr is not
NULL, it is assumed that T::newGuy() exists.

// Use #f 1 for the One Template Method
// Use #f 0 for the Two Template Method

# if 1 /////////// One Template Method /////////////

// This implementation fails if T is an abstract class,
// even if the abstract pointer is not NULL. The error
// is at compile time.

template< typename T >
T* NewGuy( const T* ptr = nullptr )
{
if( nullptr == ptr )
{
return new T;
}

return ptr->newGuy();
}

# else ////////// Two Template Methods ///////////////

// This implementation works

#include "boost/type_traits.hpp"
#include "assert.h"

// Accepts only abstract classes
template< typename T >
typename std::enable_if< boost::is_abstract<T>::value,
T* >::type
NewGuy( const T* ptr )
{
assert( ptr != nullptr );

return ptr->newGuy();
}

// Accepts only concrete classes
template< typename T >
typename std::enable_if< !boost::is_abstract<T>::value,
T* >::type
NewGuy( const T* ptr = nullptr )
{
if( nullptr == ptr )
{
return new T;
}

return ptr->newGuy();
}

# endif ////////////////////////////////////////////

#include <memory>

struct B
{
virtual B* newGuy( void ) const = 0;
};

struct A : public B
{
A* newGuy( void ) const
{
return new A;
}
} a;

int main( int argc, char* argv[] )
{
// This fails with the One Template Method
std::unique_ptr<B> bp( NewGuy( (B*) &a ) );

return 0;
}
 
J

James Kanze

Norman said:
The following program compiles and links.
No warnings The output is wrong, stating:
value= 1
value NOT 1

//////////////////////////////////////////////
#include <iostream>
using namespace std;

static constexpr int value = 1;

int main( int argc, char* argv[] )
{
cout << "value= "
<< value
<< endl;

static_assert( value == 1, "Value_Not_1" );

#if value == 1
cout << "value 1" << endl;
#else
cout << "value NOT 1" << endl;
#endif

return 0;
}// main
/////////////////////////////////////////////////

Why is value not used in the conditional compilation?
(The static_assert is happy.)
Because the conditional part is handled by the preprocessor, not the
compiler. The symbol "value" is unknown to the preprocessor so the first
condition isn't compiled.

To be more exact: when evaluating a preprocessor expression,
symbols which are unknown to the preprocessor are replaced with
0.
 
Ö

Öö Tiib

I think that was a horrible idea from the people who first designed
the C preprocessor. But I suppose it's too late to change it now.

Why? C++ is hopefully going there. First was that dual meaning of
'const' to get rid of #define constants. That 'const' was fortunately
fixed by 'constexpr'. 'constexpr' also fixed the remaining issues of
using inlines instead of macros. Now what is missing is some replacement
to #include (like modules). Also better debugging support (to replace
those __LINE__ and __FILE__).
 
I

Ian Collins

Juha said:
What do you mean why? How exactly is it a good thing that unknown
symbols are replaced with 0 instead of giving an error message?

Lots of system headers rely on this behaviour. Whether this is a good
think or not is open for debate, but it does make sense for checks to
return false if the value is undefined. I guess checks for __cplusplus
being greater than some version in a header shared by C and C++ would be
one example.
 
I

Ian Collins

David said:
I prefer to enable the gcc "-Wundef" warning in all my code - this turns
pre-processor references to undefined macros into warnings rather than
0. gcc disables such warnings for system headers (unless you ask it not
to), so it doesn't affect them - and it helps catch bugs in your own
code. It's not hard to use "#ifdef XXX" rather than "#if XXX" for
symbols that might be undefined.

Or as C++ programmers, not to use the preprocessor at all! You can't
use #if for a comparison and comparisons often occur in a complex
expression so not defined = 0 makes sense.
 
I

Ian Collins

David said:
Perhaps I am misunderstanding you, but you certainly /can/ use "#if" for
comparisons:

Sorry, I should have written "You can't use #ifdef for a comparison".
 
I

Ian Collins

Fred said:
"Ian Collins" wrote in message news:[email protected]...

But you can use
#if defined(value) && value == 0

You missed my earlier point that comparisons often occur in a complex
expression, so this isn't convenient.

By way of an example, from stdio.h on my box:

/*
* The following are known to POSIX.1c, but not to ANSI-C or XOPEN.
*/
#if defined(__EXTENSIONS__) || defined(_REENTRANT) || \
(_POSIX_C_SOURCE - 0 >= 199506L)
 
Ö

Öö Tiib

What do you mean why? How exactly is it a good thing that unknown
symbols are replaced with 0 instead of giving an error message?

Some compilers and static analyze tools can be set up to warn.
"Why" in sense "why you suppose that it is too late?". The part you
snipped tried to elaborate how C++ is actually evolving to reduce
preprocessor usage.

I do not like the preprocessor because it is hard to debug it and
because it is prime source of long compiling times. Average C++
compilation unit is hundreds of thousands of lines long after
preprocessing. Mostly pointlessly.
 
J

James Kanze

I prefer to enable the gcc "-Wundef" warning in all my code - this turns
pre-processor references to undefined macros into warnings rather than
0. gcc disables such warnings for system headers (unless you ask it not
to), so it doesn't affect them - and it helps catch bugs in your own
code. It's not hard to use "#ifdef XXX" rather than "#if XXX" for
symbols that might be undefined.

Or even "#if defined(XXX) && XXX > 0" (although I'm not sure
that the preprocessor short circuits, so you might get the
warning anyway). But I do like the idea of getting a warning.
 
W

woodbrian77

Why? C++ is hopefully going there. First was that dual meaning of
'const' to get rid of #define constants. That 'const' was fortunately
fixed by 'constexpr'. 'constexpr' also fixed the remaining issues of
using inlines instead of macros. Now what is missing is some replacement
to #include (like modules). Also better debugging support (to replace
those __LINE__ and __FILE__).


addrinfo* getaddrinfo_wrapper (char const*, int, int, int);

#define GETADDRINFO_RES(node, port, flags, socktype) \
auto deleter = [](addrinfo* p) { ::freeaddrinfo(p); }; \
::std::unique_ptr<addrinfo, decltype(deleter)> \
res(getaddrinfo_wrapper(node, port, flags, socktype), deleter);

This may not be what you mean, but was thinking about changing
that macro to be an inline function that returns a unique_ptr,
but don't think I can do that, since deleter wouldn't be in scope.


Brian
Ebenezer Enterprises - John 3:16.
http://webEbenezer.net
 
Ö

Öö Tiib

Why? C++ is hopefully going there. First was that dual meaning of
'const' to get rid of #define constants. That 'const' was fortunately
fixed by 'constexpr'. 'constexpr' also fixed the remaining issues of
using inlines instead of macros. Now what is missing is some replacement
to #include (like modules). Also better debugging support (to replace
those __LINE__ and __FILE__).


addrinfo* getaddrinfo_wrapper (char const*, int, int, int);

#define GETADDRINFO_RES(node, port, flags, socktype) \
auto deleter = [](addrinfo* p) { ::freeaddrinfo(p); }; \
::std::unique_ptr<addrinfo, decltype(deleter)> \
res(getaddrinfo_wrapper(node, port, flags, socktype), deleter);

This may not be what you mean, but was thinking about changing
that macro to be an inline function that returns a unique_ptr,
but don't think I can do that, since deleter wouldn't be in scope.

You mean there is some reason why something like that does not work
better than the macro:

struct addrinfo;

typedef std::unique_ptr< addrinfo, void(*)(addrinfo*) > ResPtr;

inline
ResPtr makeResPtr(char const* node, int port, int flags, int socktype)
{
void freeaddrinfo(addrinfo*);
addrinfo* getaddrinfo_wrapper (char const*, int, int, int);

return ResPtr( getaddrinfo_wrapper(node, port, flags, socktype)
, ::freeaddrinfo);
}

// usage:
// ResPtr res = makeResPtr(node, port, flags, socktype);

What is the reason?
 
J

James Kanze

There are certain situations where a preprocessor macro has no good
substitute in either C or C++. assert() would be a good example
(at least if you want the file and line number of the assertion itself
into the message.)

Even without the line number. The expansion of assert changes
depending on whether a macro is defined or not, and can change
several different times in a single translation unit:

#define NDEBUG
#include <assert.h>
// ...
#undef NDEBUG
#include <assert.h>
// critical section, assert slows things down too much...
#define NDEBUG
#include <assert.h>
 
Ö

Öö Tiib

Although that's not really the fault of preprocessing, rather C++'s
lack of a proper module concept.

Lack of proper module concept and lack of proper separation of interface
from implementation are because we have #include preprocessor
directive and include files inherited from C already. That helps with the
problem by causing those multimegabyte files. So it feels fault of
preprocessing in a way.
 
Ö

Öö Tiib

I think that's looking at it backwards. We abuse the #include
facility to provide some semblance of interfaces because there is not
proper module concept in C++. We may be doing it for historical
reasons, but that's not #include's fault.

Ok, maybe I somehow look at things backwards. Can you say again what
combination of these claims you suggest:

1) There was no .c and .h file pairs in C before C++ was designed.
2) The .c and .h file pairs of C did not cause that more proper
alternative module support was not designed into C++ language.
3) Lack of module support in C++ caused that we invented abuse of #include
directive and .cpp and .hpp file pairs.
4) Those .cpp and .hpp file pairs were loaned back into C as .c and .h
file pairs.

Since AFAIK those claims are all untrue it feels that I perhaps totally
misunderstand what you are trying to say.
 
G

Gerhard Fiedler

Öö Tiib said:
Ok, maybe I somehow look at things backwards. Can you say again what
combination of these claims you suggest:

1) There was no .c and .h file pairs in C before C++ was designed.
2) The .c and .h file pairs of C did not cause that more proper
alternative module support was not designed into C++ language.
3) Lack of module support in C++ caused that we invented abuse of #include
directive and .cpp and .hpp file pairs.
4) Those .cpp and .hpp file pairs were loaned back into C as .c and .h
file pairs.

Since AFAIK those claims are all untrue it feels that I perhaps totally
misunderstand what you are trying to say.

(I'm not him, but anyway...) Maybe

5) The existence of a preprocessor with its #include directive does not
in any way prevent the introduction of a proper concept of a 'module'.

?

(FWIW, it may make it seem less necessary, but that's something entirely
different.)

Gerhard
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,014
Latest member
BiancaFix3

Latest Threads

Top