Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ logging source line of an object instantiation as a constant expression without a macro

I'm playing around with std::source_location as a means to define a static identifier for a class which behaves similar to a compile-time counter. The basic idea is rather simple: use a class template taking an integral_constant as template parameter and at the site of instantiation pass the constant expression std::source_location::current().line() to the class.

With the following code using a macro, I can get this behavior without explicitly spelling the std::source_location:

#include <iostream>
#include <source_location>

template<typename, typename index>
struct A_impl
{
    static constexpr auto line() { return index::value; }
};

template<template<typename ...> typename temp, typename index>
struct line_manager
{
    template<typename ... Ts>
    using type = temp<Ts ..., index>;
};

#define A typename line_manager<A_impl, std::integral_constant<int, std::source_location::current().line()> >::type

This can be used as in the following code and allows to separate the two object instantiations at compile time:

int main()
{
    A<double> a; //expands to A_impl<double, integral_constant<int, line()> >
    A<double> b;
    
    if constexpr(a.line() != b.line())
    {
        std::cout<<a.line()<<std::endl;
        std::cout<<b.line()<<std::endl;
    }
}

Demo on Godbolt

Can the same behavior as in the code above -- i.e. logging the source line as a constant expression without explicitly specifying it in the instantiation -- can also be obtained without a macro?

Side note: I know that the above code is not functional, as you could also write A<double> a,b and obtain the same line. That doesn't matter here.

like image 403
davidhigh Avatar asked Oct 26 '25 08:10

davidhigh


1 Answers

I come up a way without using macros or direct spelling std::source_location::current().line(), but still needs a bit extra work.

The main idea is to construct a literal type that contains the line number since std::source_location is not literal.

struct Line {
    int line;
    constexpr Line(std::source_location loc = std::source_location::current()): line(loc.line()) {}
};

According to cppref:

If current() is used in a default argument, the return value corresponds to the location of the call to current() at the call site.

So we don't construct a Line until we really need it.

template <typename T, Line L>
struct Logger {
    constexpr static auto line() {
        return L.line;
    }
};

Here Logger is equivalent to A in your case. Then here we go,

Logger<int, {}> x; // x.line() refer to here
Logger<int, {}> y; // y.line() refer to here
std::cout << x.line() << "\n";
std::cout << y.line() << "\n";

Demo on GCC and MSVC

The only extra thing here you need is to pass {} as the second template argument.

(Clang complains, you have to spell Logger<int, Line{}> and you'll get the wrong results. It seems Clang's std::source_location::current().line() implementation doesn't conform the standard. Just let it go...)


You may wonder what if you just use {} as the default argument for Line.

See Demo, GCC works as expected but MSVC refers to the line template <typename T, Line line = {} resides.

BUT I think maybe MSVC conforms more to the standard here.

like image 181
Nimrod Avatar answered Oct 28 '25 22:10

Nimrod



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!