One thing I don't like about references in C++...

Let's say I'm searching through a codebase for "fooVar", because I want to see all the places that the fooVar variable gets set.

I get line-item results like:
(#1) fooVar(other_var),
(#2) fooVar = somethingElse;
(#3) fooVar_(blah.fooVar),
(#4) if (fooVar == SomeVar) {
(#5) SetParams(controller->getX(), fooVar);


#1 - is a member initialization, so it is set there. It could also be an overload of (), but either way it could be altering its value.
#2 - it's an assignment, so that's another place it's being set.
#3 - I know just from being familiar with the codebase that while this could change blah.fooVar, it's just a member initialization passthrough.
#4 - just being used in an if-statement, not changing until there's some evil operator overloading
#5 - the variable is being passed into a function

Let's say I'm not familiar with SetParams. That function could be passing fooVar by reference, so I can't immediately discard it from my search results without going and navigating to the function and checking its signature.

Technically, in C++, #1, #3, #4, #5 could all change the value of fooVar when called, but in C, I know it could only ever be #2 that syntactically could set fooVar.
Other languages, like C#, force both the calling side and the function signature side to have the "ref" keyword to quickly let the user know that reference shenanigans are happening. But C++ doesn't have that.
C forces any reference-like behavior through passing pointers, so for the value of fooVar to change, you'd need to pass it like &fooVar. Although, this becomes less useful when passing structs around by pointer for efficiency.
I like C#'s method the best. I don't know how other languages handle this. I believe Python has no reference semantics.

/rant
Last edited on
I've been bit with passing by reference into a function, as you point out the ref can hide whether the object can be modified without looking at the actual parameter list.

I deal with WinAPI code enough to (at times) I kinda prefer passing by pointers so it is obvious at the point of calling the function the parameter may be modified.

I personally deal only with C/C++/WinAPI code, no other programming languages, the benefits and detriments of being a self-taught programming hobbyist.
The programmer can still mess with you in C:
 
#define SetParams(x, y) ReallySetParams((x), &(y)) 
I don't think in C/++ you can really get away with not knowing the meanings of all the symbols in an expression in order to understand it.

Regarding C#'s syntax, it's worth noting that its explicitness is deceiving. If fooVar is not of a value type, SetParams() may still modify the contents of the referenced object as well as call any of its members with no restrictions, and unlike in C++ there's no way to specify the function in a way that makes this impossible.
Last edited on
The programmer can still mess with you in C

Oh, my, absolutely. that seems to be a prime quality of the WinAPI. Making a mockery and mess of trying to write code for Windows.
Yeah macros are gross, true, but I'm going to handwave that away as something a reasonable person™ would not do.

But yes, since in C# all non-value types are essentially pointers but without the same kind of const-correctness (and 'readonly' can only be used on fields), C# has its own disadvantages when it comes to figuring out what is mutating what.

At the end of the day I guess it's just about having a sane, consistent coding style so you minimize surprises as you look through a code base, but that's easier said than done.
Reasonable People™ use macros all the time to do stuff. A macro’s whole purpose is to make coding easier and improve correctness.

If you play evil tricks with them, then that’s problematic, sure, but if you are up-front about what tricks you are using, then I see no harm.

For example, I’m currently using PIMPL to do manage some back-end data attached to a user-facing class, and accessing the structure is not pretty. So I wrote a macro:

 
#define my (*(singleton::get_instance()->_pimple_data)) 

At first blush, people pooh-pooh macros like this as lazy and obfuscative, complaining they don’t really know what’s going on in something like:

1
2
  if (my.whazzit)
    EngageQBot( my.quux, my.blander * 3 * xz, &Quazinator.get() ); 

Go ahead, tell me that’s more evil than the following. Tell me it’s easier to know what’s really going on here:

1
2
3
4
5
  if (singleton::get_instance()->_pimple_data->whazzit)
    EngageQBot(
      singleton::get_instance()->_pimple_data->quux,
      singleton::get_instance()->_pimple_data->blander * 3 * xz, 
      &Quazinator.get() ); 

(This fake example is no where near as messy as some of the code I have to write to frob the Q library — and it isn’t a poorly-designed library — it’s actually a pretty standard C interface! It gets even harder to read the more one’s eyes naturally unfocus with the number of lines using PIMPLed data.)

Sure, say the nay-sayers, just create a “compiler tosses it away for free” variable:

1
2
  auto & my = *(singleton::get_instance()->_pimple_data); 
  ...

I’ll argue back that that’s just an instance of Variations On A Theme™, except now I’ve got to repeat the messy part over and over and over. A single macro, nicely annotated with a big fat comment at the top of the file, better embodies ODR, methinks.

Sure, it’s Evil™. But it does its job cleanly, which is to make code more readable and significantly more difficult to introduce errors.


So... I 100% agree that:
At the end of the day I guess it's just about having a sane, consistent coding style so you minimize surprises as you look through a code base...



That all said, I’m a crusty old hack with opinions about stuff, so, take it with a grain of salt.
I should have phrased my previous post better. I didn't mean to imply all uses of macros were evil. helios's macro hides the fact that it's taking the address of something. Yours doesn't.

Also, if we stick with the de facto standard convention of macros being ALL_CAPS, then if I'm searching through code and see an unfamiliar macro using a variable, then that at least gives me reason to look at that line of code closer instead of brushing it off as simply using the variable and not changing the variable. I'm all for trying to make things more readable.
Heh, I understood. I’m just being curmudgeonly. Sorry.

I think you and I are pretty much on the same page. I just don’t want to commit to saying ‘all X are Evil’.

I have to agree that hiding referencing is Evil.

Languages like Java and C# that use references for everything drive me more bonkers, though, but I can’t say there’s a rational reason for that.

Personally, I find functional languages where everything is immutable to set my pins straight. They play just like languages where that is not true, but you absolutely cannot accidentally goober state for some unexpected reference elsewhere.
I think the best thing macros are good at is stringizing and concatenating identifiers. One of my favorite uses is initializing dynamic interfaces:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define DECLARE_FUNCTION_POINTER(x) x##_f x##_p
#define INIT_FUNCTION(x) x##_p = nullptr
#define RESOLVE_FUNCTION(x) {       \
    x##_p = (x##_f)lib.resolve(#x); \
    if (!x##_p)                     \
        return;                     \
}

typedef const char *(*get_foo_f)();
typedef int (*get_bar_f)();

DECLARE_FUNCTION_POINTER(get_foo);
DECLARE_FUNCTION_POINTER(get_bar);

INIT_FUNCTION(get_foo);
INIT_FUNCTION(get_bar);

RESOLVE_FUNCTION(get_foo);
RESOLVE_FUNCTION(get_bar);


But my absolute favorite is
1
2
3
4
5
6
#define CONCAT_HELPER(x, y) x##y
#define CONCAT(x, y) CONCAT_HELPER(x, y)
#define LOCK_MUTEX(x) std::lock_guard<decltype(x)> CONCAT(lg_,__COUNTER__)(x)

std::mutex mutex;
LOCK_MUTEX(mutex);
My favorite use is actually providing default arguments in C, lol.

Here’s a custom-made example I posted a while back: https://stackoverflow.com/a/69512860/2706707

This is part of my libduthomhas library that someday (soon) I’ll get up on Github.

  c-default-args.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#ifndef DUTHOMHAS_C_DEFAULT_ARGUMENTS_H
#define DUTHOMHAS_C_DEFAULT_ARGUMENTS_H

// Copyright 2021 Michael Thomas Greer
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
//  https://www.boost.org/LICENSE_1_0.txt )

// This is a VERY simple library to provide default argument capabilities to C functions.
// I suppose it is possible to dive very much deeper down the preprocessor rabbit hole
// and make using this really short and pretty, but the cost is enormous and this suffices
// me. Example usage:
//
//   #include <duthomhas/c-default-args.h>
//
//   int my_function( int a, int b, int c );
//   #define my_function(...) DUTHOMHAS_CALL_OVERLOAD(my_function_,__VA_ARGS__)
//   #define my_function_0()      my_function( -7, 512, 42 )
//   #define my_function_1(a)     my_function(  a, 512, 42 )
//   #define my_function_2(a,b)   my_function(  a,   b, 42 )
//   #define my_function_3(a,b,c) my_function(  a,   b,  c )
//
//   int x = my_function();            // same as: int x = my_function( -7, 512, 42 );
//   int y = my_function( 15, 4000 );  // same as: int y = my_function( 15, 4000, 42 );
//
// The equivalent construct in C++ would be:
//
//   int my_function( int a=-7, int b=512, int c=42 );
//
// Do not define my_function_N for the argument lists that are not permitted and let the
// compiler complain for you if the user did not provide a sufficiently large number of arguments.
//
//   #define my_function(...) DUTHOMHAS_CALL_OVERLOAD(my_function_,__VA_ARGS__)
//   #define my_function_1(a)     my_function( a, M_PI, 74 )
//   #define my_function_3(a,b,c) my_function( a, b,    c  )
//
//   int x = my_function();                // compile error
//   int y = my_function( 3 );             // OK
//   int y = my_function( 3, 5 );          // compile error
//   int z = my_function( 3, 5, 7 );       // OK
//
// And, naturally, providing too many arguments will lead to compiler error as well.
//
//   int q = my_function( 1, 2, 3, 4, 5 ); // compiler error
//
// The compile error will look something like:
//
//   "implicit declaration of function 'my_function_0' is invalid"
//   "unresolved external symbol my_function_0 referenced in function main"

#ifdef __clang__
  #pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#endif

// Francesco Pretto (ceztko) --> https://stackoverflow.com/a/26685339/2706707

#ifdef _MSC_VER // Microsoft compilers

  #define DUTHOMHAS_EXPAND(x) x
  #define DUTHOMHAS_NARGS_0(_1, _2, _3, _4, _5, VAL, ...) VAL
  #define DUTHOMHAS_NARGS_1(...) DUTHOMHAS_EXPAND(DUTHOMHAS_NARGS_0(__VA_ARGS__, 4, 3, 2, 1, 0))

  #define DUTHOMHAS_AUGMENTER(...) unused, __VA_ARGS__
  #define DUTHOMHAS_NARGS(...) DUTHOMHAS_NARGS_1(DUTHOMHAS_AUGMENTER(__VA_ARGS__))

#else // Others

  #define DUTHOMHAS_NARGS(...) DUTHOMHAS_NARGS_0(0, ## __VA_ARGS__, 5,4,3,2,1,0)
  #define DUTHOMHAS_NARGS_0(_0,_1,_2,_3,_4,_5,N,...) N

#endif

// Braden Steffaniak --> https://stackoverflow.com/a/24028231/2706707

#define DUTHOMHAS_GLUE(x, y) x y

#define DUTHOMHAS_OVERLOAD_MACRO2(name, count) name##count
#define DUTHOMHAS_OVERLOAD_MACRO1(name, count) DUTHOMHAS_OVERLOAD_MACRO2(name, count)
#define DUTHOMHAS_OVERLOAD_MACRO(name, count) DUTHOMHAS_OVERLOAD_MACRO1(name, count)

#define DUTHOMHAS_CALL_OVERLOAD(name, ...) DUTHOMHAS_GLUE(DUTHOMHAS_OVERLOAD_MACRO(name, DUTHOMHAS_NARGS(__VA_ARGS__)), (__VA_ARGS__))

#endif 

Notice the attributions in the code — I learned how to do this stuff on the interwebs!
This is part of my libduthomhas library that someday (soon) I’ll get up on Github.

Please do so! I like to see (and use) well written code snippets. Especially when they are small and easy to understand.

I put my custom libraries up on GitHub, though my work really isn't all that spectacular.

https://github.com/GeorgePimpleton/cpp_misc_files

My random toolkit was inspired by a C++ working paper I read years ago: WG21 N3551.

http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3551.pdf



Another thing I find myself using macros for more and more often is the “X macro trick” (https://en.wikipedia.org/wiki/X_macro).

The basic idea is to ODR your data.


Just found myself doing it, and remembered this thread, so... I figured I’d add some entropy to the universe.
Topic archived. No new replies allowed.