14

Is it possible, using the C/C++ preprocessor, to count lines within a source file, into either a macro or some kind compile-time-available value? E.g. can I replace MAGIC1, MAGIC2 and MAGIC3 in the following, and get the value 4 somehow when using MAGIC3?

MAGIC1 // can be placed wherever you like before the relevant 
       // lines - either right before them, or in global scope etc.
foo(); MAGIC2
bar(); MAGIC2
baz(); MAGIC2
quux(); MAGIC2
// ... possibly a bunch of code here; not guaranteed to be in same scope ...
MAGIC3

Notes:

  • Compiler-specific extensions to the preprocessor's capabilities are acceptable but undesirable.
  • If this is possible only with the help of some of C++, as opposed to C, construct, that's also acceptable but undesirable (i.e. I'd like something that would work for C).
  • Obviously this can be done by running the source file through some external processor script, but that's not what I'm asking.
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 6
    There's [a macro called `__LINE__`](https://www.cprogramming.com/reference/preprocessor/__LINE__.html) that represents the current line number – ForceBru Jan 08 '20 at 10:49
  • you want to count only lines annotated with `MAGIC2`, right? – 463035818_is_not_an_ai Jan 08 '20 at 10:51
  • 2
    Are searching for `__COUNTER__` and/or `BOOST_PP_COUNTER`? – KamilCuk Jan 08 '20 at 10:53
  • 11
    What is the *actual* problem you need to solve? Why do you need this? – Some programmer dude Jan 08 '20 at 10:58
  • 1
    Does [this](https://stackoverflow.com/questions/28724307/macro-based-counter) help? – user1810087 Jan 08 '20 at 10:59
  • Assuming a hypothetical preprocessor-based solution, what kind of preprocessed C code are you trying to get? – Petr Skocik Jan 08 '20 at 11:01
  • @formerlyknownas_463035818: Yes, but that's just one possibly approach I had in my mind for achieving the line counting. – einpoklum Jan 08 '20 at 11:02
  • 1
    @PSkocik: I want something I could use as a compile-time constant, e.g. for saying `int arr[MAGIC4]` and get the number of lines in some previously-counted section of my code. – einpoklum Jan 08 '20 at 11:04
  • @user1810087: That is one approach (in which we actively write something to increase the counter with every line). But the question there seems focused on using Boost's preprocessor utilities. – einpoklum Jan 08 '20 at 11:18
  • 1
    I'm still really curious what kind of problem requires the code to have data structures based on its own code formatting/layout. Is this for some code golf challenge? :D – Max Langhof Jan 08 '20 at 11:56
  • 1
    @MaxLanghof: Actually, unit tests. I want to have each line check something, and append the result to some array, the memory for which - for certain reasons - needs to be allocated statically. But I don't want to spell out how many checks I have. – einpoklum Jan 08 '20 at 12:01

5 Answers5

15

There is the __LINE__ preprocessor macro which gives you an integer for the line is appeared on. You could take its value on some line, and then some later line, and compare.

static const int BEFORE = __LINE__;
foo();
bar();
baz();
quux();
static const int AFTER = __LINE__;
static const int COUNT = AFTER - BEFORE - 1; // 4

If you want to count the occurrences of something rather than source lines, __COUNTER__ might be a non-standard option, supported by some compilers such as GCC and MSVC.

#define MAGIC2_2(c)
#define MAGIC2(c) MAGIC2_2(c)
static const int BEFORE = __COUNTER__;
void foo(); MAGIC2(__COUNTER__);
void bar(
    int multiple,
    float lines); MAGIC2(__COUNTER__);
void baz(); MAGIC2(__COUNTER__);
void quux(); MAGIC2(__COUNTER__);
static const int AFTER = __COUNTER__;
static const int COUNT = AFTER - BEFORE - 1; // 4

I took the initial value of __COUNTER__ because it might have been used previously in the source file, or some included header.

In C rather than C++ there are limitations on constant variables, so an enum might be used instead.

enum MyEnum
{
    FOO = COUNT // C: error: enumerator value for ‘FOO’ is not an integer constant
};

Replacing the const with enum:

enum {BEFORE = __LINE__};
foo();
bar();
baz();
quux();
enum { COUNT = __LINE__ - BEFORE - 1};
enum MyEnum
{
    FOO = COUNT // OK
};
Fire Lancer
  • 29,364
  • 31
  • 116
  • 182
  • I think this is about the closest as you can get with just the preprocessor. The preprocessor is one-pass, so you can't backpatch a later computed value, but global-variable references will work and should optimize the same. They just won't work at integer constants expressions, but it might be possible to structure the code so that those aren't needed for the counts. – Petr Skocik Jan 08 '20 at 11:03
  • 2
    `__COUNTER__` is not standard in C or C++. If you know it works with particular compilers, specify them. – Peter Jan 08 '20 at 11:05
  • @einpoklum no, `BEFORE` and `AFTER` aren't macros – Alan Birtles Jan 08 '20 at 11:07
  • There's a problem with the non-counter version of this solution: the before and after are usable only in the same scope as the source lines. Edited my "e.g." snippet to reflect that this is an issue. – einpoklum Jan 08 '20 at 11:23
  • Note that this solution will not work on standard C compiler, because constant variables cannot be used in constant expressions. To get around that, you should declare `BEFORE` and `AFTER` as enum constants. – user694733 Jan 08 '20 at 11:29
  • 1
    @user694733 True question was tagged [C++]. For C enum constants work. – Fire Lancer Jan 08 '20 at 11:31
9

I know that the OP's request is to use macros, but I would like to add another way of doing this that does not involve using macros.

C++20 introduces the source_location class that represents certain information about the source code, such as file names, line numbers, and function names. We can use that pretty easily in this case.

#include <iostream>
#include <source_location>

static constexpr auto line_number_start = std::source_location::current().line();
void foo();
void bar();
static constexpr auto line_number_end = std::source_location::current().line();

int main() {
    std::cout << line_number_end - line_number_start - 1 << std::endl; // 2

    return 0;
}

And live example here.

NutCracker
  • 11,485
  • 4
  • 44
  • 68
  • Without macros is even better than with macros. However - with this approach, I can only use the line count in the same scope as the lines I've counted. Also - with `source_location` be experimental in C++20? – einpoklum Jan 08 '20 at 13:26
  • I agree that solution without macros is far better than with macros. `source_location` is now officially part of C++20. Check [here](https://en.cppreference.com/w/cpp/utility/source_location). I just couldn't find the version of gcc compiler on [godbolt.org](https://godbolt.org/) that already supports it in non experimental sense. Can you please explain a bit more your statement - *I can only use the line count in the same scope as the lines I've counted*? – NutCracker Jan 08 '20 at 13:30
  • Suppose I put your suggestion within a function (i.e. the lines counted are invocations, not declarations). It works - but I only have `line_number_start` and `line_number_end` within that scope, nowhere else. If I want it elsewhere I need to pass it at run-time - which defeats the purpose. – einpoklum Jan 08 '20 at 13:35
  • Take a look at the example that standard provides [here](https://en.cppreference.com/w/cpp/utility/source_location). If it is default argument, then it is still part of compile-time, right? – NutCracker Jan 08 '20 at 13:46
  • Yes, but that doesn't make `line_number_end` visible at compile-time outside its scope. Correct me if I'm wrong. – einpoklum Jan 08 '20 at 14:29
  • Maybe you could update your answer with an example because I think I didn't get what you think. Thanks in advance – NutCracker Jan 08 '20 at 16:43
  • @NutCraker: Done. – einpoklum Jan 08 '20 at 17:06
  • @einpoklum will take a look at it a bit later this evening – NutCracker Jan 08 '20 at 17:55
  • @einpoklum I tried few things but could not get it to work at compile-time. So I suppose it's either not possible or I don't have enough knowledge to get it working. Btw I tried running your example from your answer but `num_counted_lines` is still 0 at the end. – NutCracker Jan 09 '20 at 07:11
7

For completeness: If you're willing to add MAGIC2 after every line, you can use __COUNTER__:

#define MAGIC2 static_assert(__COUNTER__ + 1, "");

/* some */     MAGIC2
void source(); MAGIC2
void lines();  MAGIC2

constexpr int numberOfLines = __COUNTER__;

int main()
{
    return numberOfLines;
}

https://godbolt.org/z/i8fDLx (returns 3)

You can make it reusable by storing the start and end values of __COUNTER__.

Overall this is really cumbersome though. You also won't be able to count lines that contain preprocessor directives or that end with // comments. I'd use __LINE__ instead, see the other answer.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
7

A somewhat more robust solution, allowing for different counters (as long as they don't intermix, and there's no use of __COUNTER__ for other tasks):

#define CONCATENATE(s1, s2) s1##s2
#define EXPAND_THEN_CONCATENATE(s1, s2) CONCATENATE(s1, s2)

#define COUNT_THIS_LINE static_assert(__COUNTER__ + 1, "");
#define START_COUNTING_LINES(count_name) \
  enum { EXPAND_THEN_CONCATENATE(count_name, _start) = __COUNTER__ };
#define FINISH_COUNTING_LINES(count_name) \
  enum { count_name = __COUNTER__ - EXPAND_THEN_CONCATENATE(count_name, _start) - 1 };

This hides the implementation details (although it hides them inside macros...). It is a generalization of @MaxLanghof's answer. Note that __COUNTER__ may have a non-zero value when we start a count.

Here's how it's used:

START_COUNTING_LINES(ze_count)

int hello(int x) {
    x++;
    /* some */     COUNT_THIS_LINE
    void source(); COUNT_THIS_LINE
    void lines();  COUNT_THIS_LINE
    return x;
}

FINISH_COUNTING_LINES(ze_count)

int main()
{
    return ze_count;
}

Also, this is valid C - if your preprocessor supports __COUNTER__, that is.

Works on GodBolt.

If you're using C++, you can modify this solution to not even pollute the global namespace - by placing the counters within namespace macro_based_line_counts { ... }, or namespace detail etc.)

einpoklum
  • 118,144
  • 57
  • 340
  • 684
5

Based on your comment, if you want to specify a (compile-time) array size in C or C++, you can do

int array[]; //incomplete type
enum{ LINE0 = __LINE__ }; //enum constants are integer constant expressions
/*lines to be counted; may use array (though not sizeof(array)) */
/*...*/
int array[ __LINE__ - LINE0 ]; //complete the definition of int array[]

If you need sizeof(array) in the intervening lines, you can replace it with a static variable reference (unless it absolutely needs to be an integer constant expression) and an optimizing compiler should treat it just the same (eliminate the need for the static variable to be placed in memory)

int array[]; static int count;
enum{ LINE0 = __LINE__ }; //enum constants are integer constant expressions
//... possibly use count here
enum { LINEDIFF = __LINE__ - LINE0 }; 
int array[ LINEDIFF ]; /*complete the definition of int array[]*/ 
static int count = LINEDIFF; //fill the count in later

A __COUNTER__-based solution (if that extension is available) as opposed to a __LINE__-based one will work the same.

constexprs in C++ should work as well as enum, but enum will work in plain C as well (my solution above is a plain C solution).

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • This will work only if my use of the line-count is in the same scope as the counted lines. IIANM. Note I edited my question slightly to emphasize that could be an issue. – einpoklum Jan 08 '20 at 11:25
  • 1
    @einpoklum A `__COUNTER__` based solution has issues too: you better hope your magic macro is the only user of `__COUNTER__`, at least before you're done with your use of `__COUNTER__`. The problem basically all comes down to the simple facts that `__COUNTER__/__LINE__` are preprocessor features and the preprocessor works in one pass, so you can't backpatch an integer constant expression later based on `__COUNTER__`/`__LINE__`. The only way (in C at least) is to avoid the need in the first place, e.g., by using forward array declarations without size (incompletely typed array declarations). – Petr Skocik Jan 08 '20 at 11:38
  • 1
    For the record, the `\ ` does not affect `__LINE__` - if there is a line break, `__LINE__` increases. [Example 1](https://godbolt.org/z/UXXPsG), [example 2](https://godbolt.org/z/6dnHuZ). – Max Langhof Jan 08 '20 at 12:01
  • @MaxLanghof Thanks. Didn't realize that. Fixed. – Petr Skocik Jan 08 '20 at 12:11